// SafeBus product page — main app component
// Loaded after data.js, tweaks-panel.jsx

const { useState, useEffect, useMemo, useCallback, useRef } = React;

// ---- Tweaks defaults (rewritten by host on edit) -----------------------
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": "classic",
  "theme": "signature",
  "heroLayout": "inline",
  "cardStyle": "default",
  "busSelectorStyle": "list",
  "kitStrategy": "all",
  "bothMountLayout": "combined"
}/*EDITMODE-END*/;

// ---- Header / brand ----------------------------------------------------
function Header({ cartCount }) {
  return (
    <header className="site-header">
      <div className="site-header__inner">
        <a className="brand" href="home.html">
          <img className="brand__logo" src="assets/logo/safebus-logo.png" alt="SafeBus" />
        </a>
        <div className="site-header__spacer"></div>
        <nav className="nav">
          <a href="#products">Products</a>
          <a href="shop.html">Shop</a>
          <a href="#install">Installation</a>
          <a href="#compliance">Compliance</a>
          <a href="#support">Support</a>
        </nav>
        <button className="cart-pill" aria-label="Cart">
          <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="9" cy="20" r="1.5"/><circle cx="18" cy="20" r="1.5"/><path d="M3 4h2l2.7 12.3a2 2 0 0 0 2 1.7h7.6a2 2 0 0 0 2-1.5L21 8H6"/></svg>
          CART
          <span className="cart-pill__count">{cartCount}</span>
        </button>
      </div>
    </header>
  );
}

// ---- Picker modal ------------------------------------------------------
function Picker({ kind, value, onClose, onSelect, style, askSide, askMount, bothMountLayout, stateCode }) {
  const isBus = kind === 'bus';
  const isCategory = kind === 'category';
  const items = isBus ? window.BUSES : isCategory ? window.CATEGORIES : window.STATES;
  const useGrid = isBus && style === 'grid';
  // Bus picker is a small wizard: grid → (body shape) → (front/rear/both) → (mount).
  const [wizBus, setWizBus] = useState(null);
  const [wizLook, setWizLook] = useState(null);
  const [wizSide, setWizSide] = useState(null);
  // For the 'both' flow, front and rear mounts are chosen independently.
  const [wizFrontMount, setWizFrontMount] = useState(null);
  const [wizRearMount, setWizRearMount] = useState(null);
  const wizLooks = wizBus && wizBus.installPhotos && wizBus.installPhotos.looks;
  const wizNeedsLook = !!(wizLooks && wizLooks.length > 1);

  // Why is a mount tile blocked? If the OTHER mount on that side is available,
  // it's a fitment rule ("rear must be external"). If neither is available, the
  // state's compliance regime blocks it (e.g. NSW TS-150 with no specific photo).
  const blockReason = (endSide, mountId) => {
    const otherMount = mountId === 'internal' ? 'external' : 'internal';
    if (window.mountAvailable(wizBus, wizLook, endSide, otherMount, stateCode)) {
      return `Not compliant \u2014 ${endSide} must be ${otherMount}`;
    }
    return (window.STRICT_PHOTO_STATES || []).includes(stateCode)
      ? `Not road legal in ${stateCode}`
      : 'Not road legal on this bus';
  };

  const SIDE_OPTS = [
    { id: 'front', label: 'Front' },
    { id: 'rear',  label: 'Rear' },
    { id: 'both',  label: 'Both' },
  ];

  // Click a bus tile in the grid.
  const pickBus = (it) => {
    const looks = it.installPhotos && it.installPhotos.looks;
    const hasLooks = looks && looks.length > 1;
    if (hasLooks) { setWizBus(it); setWizLook(null); setWizSide(null); }              // → body-shape step
    else if (askSide || askMount) { setWizBus(it); setWizLook(null); setWizSide(null); } // → side / mount step
    else { onSelect(it.id, null, undefined, undefined); onClose(); }                  // done
  };
  // Click a body-shape tile.
  const pickLook = (lookId) => {
    if (askSide || askMount) { setWizLook(lookId); }         // → side step (lights) or mount step (kits)
    else { onSelect(wizBus.id, lookId, undefined, undefined); onClose(); }
  };
  // Click a front/rear/both tile → advance to mount step.
  const pickSide = (sideId) => {
    setWizSide(sideId);
    setWizFrontMount(null);
    setWizRearMount(null);
  };
  // Click an internal/external tile → finish (single-side flow, incl. kits).
  // Kits skip the side step, so wizSide is null — treat that as 'both'.
  // Normalise a mount tile id to the mount STYLE used for product matching.
  // Extra positions like 'internalRight' still match as 'internal'.
  const mountStyleOf = (mountId) => (mountId && mountId.indexOf('external') === 0) ? 'external' : 'internal';
  const pickMount = (mountId) => {
    onSelect(wizBus.id, wizNeedsLook ? wizLook : null, wizSide || 'both', mountStyleOf(mountId));
    onClose();
  };
  // Finish the 'both' flow with separate front + rear mounts.
  const finishBoth = (fm, rm) => {
    onSelect(wizBus.id, wizNeedsLook ? wizLook : null, 'both', { front: mountStyleOf(fm), rear: mountStyleOf(rm) });
    onClose();
  };

  // Which wizard stage are we on?  'grid' | 'look' | 'side' | 'mount'
  // Kits (askMount, !askSide) skip the side step and land straight on mount.
  const stage = !wizBus ? 'grid'
    : (wizNeedsLook && wizLook == null) ? 'look'
    : (askSide && wizSide == null) ? 'side'
    : 'mount';

  const titles = {
    bus:      { t: 'Which bus?',                  s: 'Pick the make or chassis. We support every common Australian bus.' },
    state:    { t: 'Which state or territory?',   s: "Different states have different compliance requirements (NSW = TS-150)." },
    category: { t: 'What are you looking for?',   s: 'Pick a product category. New categories are being added — let us know what you need.' },
  };
  const title = titles[kind];

  // --- Body-shape (sub-model) step ---
  if (stage === 'look') {
    return (
      <div className="picker-backdrop" onClick={onClose}>
        <div className="picker picker--submodel" onClick={e => e.stopPropagation()}>
          <button className="picker__back" onClick={() => { setWizBus(null); setWizLook(null); }}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
            All buses
          </button>
          <h3 className="picker__title">Which model {wizBus.sentenceName || wizBus.short}?</h3>
          <p className="picker__sub">Two body shapes fit different lights — pick the one that matches yours.</p>
          <div className="picker__grid picker__grid--submodel">
            {wizLooks.map(lk => (
              <button
                key={lk.id}
                className="picker__tile picker__tile--submodel"
                onClick={() => pickLook(lk.id)}
              >
                <div className="picker__tile-img">
                  <span className="picker__tile-face picker__tile-face--front" style={{ backgroundImage: `url(${lk.photo})`, backgroundSize: 'contain' }}></span>
                </div>
                <div className="picker__tile-label">
                  {lk.label}
                  {lk.sub && <span className="picker__tile-sublabel">{lk.sub}</span>}
                </div>
              </button>
            ))}
          </div>
        </div>
      </div>
    );
  }

  // --- Front / rear / both step ---
  if (stage === 'side') {
    const goBack = () => {
      if (wizNeedsLook) { setWizLook(null); }   // back to body-shape
      else { setWizBus(null); }                 // back to grid
    };
    return (
      <div className="picker-backdrop" onClick={onClose}>
        <div className="picker picker--submodel" onClick={e => e.stopPropagation()}>
          <button className="picker__back" onClick={goBack}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
            {wizNeedsLook ? 'Body shape' : 'All buses'}
          </button>
          <h3 className="picker__title">Lights for the front, rear or both?</h3>
          <p className="picker__sub">Pick where you need warning lights fitted on your {wizBus.sentenceName || wizBus.short}.</p>
          <div className="picker__grid picker__grid--side">
            {SIDE_OPTS.map(o => (
              <button
                key={o.id}
                className="picker__tile picker__tile--submodel"
                onClick={() => pickSide(o.id)}
              >
                <div className="picker__tile-img">
                  <span className="picker__tile-face picker__tile-face--front" style={{ backgroundImage: `url(${window.busPhoto(wizBus, wizLook, o.id, 'nolights')})`, backgroundSize: 'contain' }}></span>
                </div>
                <div className="picker__tile-label">{o.label}</div>
              </button>
            ))}
          </div>
        </div>
      </div>
    );
  }

  // --- Internal / external mount step ---
  if (stage === 'mount') {
    const MOUNT_OPTS = [
      { id: 'internal', label: 'Internal', sub: 'Inside the window' },
      { id: 'external', label: 'External', sub: 'On the body' },
    ];

    // A look's photo set may override which mount tiles appear per side (e.g.
    // coaches: front internal-only, rear external-only, plus an extra internal
    // mounting position). When present, only road-legal options for the current
    // state are shown — non-compliant ones are hidden rather than greyed out.
    const ipSet = (() => {
      const ip = wizBus && wizBus.installPhotos;
      if (!ip || !ip.byLook) return null;
      const lk = (wizLook && ip.byLook[wizLook]) ? wizLook
        : (ip.looks && ip.looks[0] && ip.looks[0].id) || Object.keys(ip.byLook)[0];
      return ip.byLook[lk] || null;
    })();
    const customMounts = ipSet && ipSet.mountOptions;
    const optsForSide = (sd) => {
      if (customMounts && customMounts[sd]) {
        return customMounts[sd]
          .map(o => ({ ...o, mountId: o.mountId || o.id }))
          .filter(o => o.showBlocked || window.mountAvailable(wizBus, wizLook, sd, o.mountId, stateCode));
      }
      return MOUNT_OPTS.map(o => ({ ...o, mountId: o.id }));
    };

    // Kits bundle front + rear, so they use the same two-mount UI as the lights
    // "both" flow — the user picks a mount for the front AND the rear.
    const isKitFlow = askMount && !askSide;

    // --- BOTH: front + rear, each get a mount choice (lights "both" OR any kit) ---
    if (wizSide === 'both' || isKitFlow) {
      const layout = bothMountLayout === 'sequential' ? 'sequential' : 'combined';
      // Back navigation differs: lights "both" returns to the side step; kits
      // return to the body-shape step (if any) or the bus grid.
      const bothBackTo = askSide
        ? () => setWizSide(null)
        : (wizNeedsLook ? () => setWizLook(null) : () => setWizBus(null));
      const bothBackLabel = askSide
        ? 'Front / rear / both'
        : (wizNeedsLook ? 'Body shape' : 'All buses');

      // A mount position is "not road legal" on a bus when its install photo is
      // absent (see window.mountAvailable). Shown but disabled with a compliance
      // note so the user understands why the option is unavailable.
      const isBlocked = (endSide, mountId) =>
        !window.mountAvailable(wizBus, wizLook, endSide, mountId, stateCode);

      const MountTile = (endSide, o, selected, onClick) => {
        const blocked = isBlocked(endSide, o.mountId);
        return (
        <button
          key={endSide + o.id}
          className={"picker__tile picker__tile--submodel " + (selected ? "is-selected " : "") + (blocked ? "is-blocked" : "")}
          onClick={blocked ? undefined : onClick}
          disabled={blocked}
          aria-disabled={blocked}
          title={blocked ? (o.blockNote || blockReason(endSide, o.mountId)) : undefined}
        >
          <div className="picker__tile-img">
            <span className="picker__tile-face picker__tile-face--front" style={{ backgroundImage: `url(${window.busPhoto(wizBus, wizLook, endSide, o.mountId, stateCode)})`, backgroundSize: 'contain' }}></span>
            {blocked && (
              <span className="picker__tile-badge">
                <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
                Not road legal
              </span>
            )}
          </div>
          <div className="picker__tile-label">
            {o.label}
            <span className="picker__tile-sublabel">{blocked ? (o.blockNote || blockReason(endSide, o.mountId)) : o.sub}</span>
          </div>
        </button>
        );
      };

      // TWEAK 2: front first, then rear on a separate dialog.
      if (layout === 'sequential') {
        const phase = wizFrontMount == null ? 'front' : 'rear';
        const goBack = () => {
          if (phase === 'rear') setWizFrontMount(null); // back to front choice
          else bothBackTo();                            // back to side / body-shape / grid
        };
        return (
          <div className="picker-backdrop" onClick={onClose}>
            <div className="picker picker--submodel" onClick={e => e.stopPropagation()}>
              <button className="picker__back" onClick={goBack}>
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
                {phase === 'rear' ? 'Front mount' : bothBackLabel}
              </button>
              <h3 className="picker__title">{phase === 'front' ? 'Front — internal or external?' : 'Rear — internal or external?'}</h3>
              <p className="picker__sub">
                {phase === 'front'
                  ? 'Choose the mount for the front lights. You\u2019ll pick the rear next.'
                  : 'Now choose the mount for the rear lights.'}
              </p>
              <div className="picker__grid picker__grid--submodel">
                {optsForSide(phase).map(o => MountTile(
                  phase,
                  o,
                  false,
                  () => {
                    if (phase === 'front') setWizFrontMount(o.id);
                    else finishBoth(wizFrontMount, o.id);
                  }
                ))}
              </div>
            </div>
          </div>
        );
      }

      // TWEAK 1: all four images on one dialog — Front and Rear side by side
      // (compact tiles) so nothing needs scrolling.
      const ready = wizFrontMount && wizRearMount;
      return (
        <div className="picker-backdrop" onClick={onClose}>
          <div className="picker picker--bothmount" onClick={e => e.stopPropagation()}>
            <button className="picker__back" onClick={bothBackTo}>
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
              {bothBackLabel}
            </button>
            <h3 className="picker__title">Internal or external mount?</h3>
            <p className="picker__sub">Pick a mount for the front and the rear — they can differ.</p>

            <div className="picker__mount-cols">
              <div className="picker__mount-group">
                <span className="picker__mount-label">Front</span>
                <div className="picker__grid picker__grid--quad">
                  {optsForSide('front').map(o => MountTile('front', o, wizFrontMount === o.id, () => setWizFrontMount(o.id)))}
                </div>
              </div>
              <div className="picker__mount-group">
                <span className="picker__mount-label">Rear</span>
                <div className="picker__grid picker__grid--quad">
                  {optsForSide('rear').map(o => MountTile('rear', o, wizRearMount === o.id, () => setWizRearMount(o.id)))}
                </div>
              </div>
            </div>

            <button
              className="picker__continue"
              disabled={!ready}
              onClick={() => ready && finishBoth(wizFrontMount, wizRearMount)}
            >
              {ready ? 'See matching products' : 'Pick a front and rear mount'}
              <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M5 12h14M13 5l7 7-7 7"/></svg>
            </button>
          </div>
        </div>
      );
    }

    // --- Single side (front or rear) — lights only; kits use the both-UI above ---
    const mountBackTo = () => setWizSide(null);  // back to front/rear/both
    const photoSide = wizSide || 'front';
    return (
      <div className="picker-backdrop" onClick={onClose}>
        <div className="picker picker--submodel" onClick={e => e.stopPropagation()}>
          <button className="picker__back" onClick={mountBackTo}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
            Front / rear / both
          </button>
          <h3 className="picker__title">Internal or external mount?</h3>
          <p className="picker__sub">Window-mounted lights sit inside the cabin; external lights fix to the body.</p>
          <div className="picker__grid picker__grid--submodel">
            {optsForSide(photoSide).map(o => {
              const blocked = !window.mountAvailable(wizBus, wizLook, photoSide, o.mountId, stateCode);
              return (
              <button
                key={o.id}
                className={"picker__tile picker__tile--submodel " + (blocked ? "is-blocked" : "")}
                onClick={blocked ? undefined : () => pickMount(o.mountId)}
                disabled={blocked}
                aria-disabled={blocked}
                title={blocked ? (o.blockNote || blockReason(photoSide, o.mountId)) : undefined}
              >
                <div className="picker__tile-img">
                  <span className="picker__tile-face picker__tile-face--front" style={{ backgroundImage: `url(${window.busPhoto(wizBus, wizLook, photoSide, o.mountId, stateCode)})`, backgroundSize: 'contain' }}></span>
                  {blocked && (
                    <span className="picker__tile-badge">
                      <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
                      Not road legal
                    </span>
                  )}
                </div>
                <div className="picker__tile-label">
                  {o.label}
                  <span className="picker__tile-sublabel">{blocked ? (o.blockNote || blockReason(photoSide, o.mountId)) : o.sub}</span>
                </div>
              </button>
              );
            })}
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="picker-backdrop" onClick={onClose}>
      <div className="picker" onClick={e => e.stopPropagation()}>
        <h3 className="picker__title">{title.t}</h3>
        <p className="picker__sub">{title.s}</p>
        {useGrid ? (
          <div className="picker__grid">
            {items.map(it => {
              const isSel = it.id === value;
              const front = it.frontImg;
              const rear  = it.rearImg;
              const hasBoth = front && rear;
              // Default face: front if available, otherwise rear.
              // Hover face: rear if both exist (so user sees the other angle).
              const defaultImg = front || rear;
              const hoverImg = hasBoth ? rear : null;
              const hasLooks = it.installPhotos && it.installPhotos.looks && it.installPhotos.looks.length > 1;
              return (
                <button
                  key={it.id}
                  className={"picker__tile " + (isSel ? "is-selected" : "") + (hoverImg ? " has-flip" : "")}
                  onClick={() => pickBus(it)}
                >
                  <div className="picker__tile-img">
                    {defaultImg ? (
                      <span className="picker__tile-face picker__tile-face--front" style={{ backgroundImage: `url(${defaultImg})` }}></span>
                    ) : (
                      <span className="picker__tile-placeholder">{it.short}</span>
                    )}
                    {hoverImg && (
                      <span className="picker__tile-face picker__tile-face--rear" style={{ backgroundImage: `url(${hoverImg})` }}></span>
                    )}
                    {hasBoth && (
                      <span className="picker__tile-flip-hint">
                        <span className="picker__tile-flip-front">front</span>
                        <span className="picker__tile-flip-rear">rear</span>
                      </span>
                    )}
                    {hasLooks && <span className="picker__tile-variants">2 shapes</span>}
                  </div>
                  <div className="picker__tile-label">{it.short}</div>
                </button>
              );
            })}
          </div>
        ) : (
          <div className="picker__list">
            {items.map(it => {
              const code = isBus ? it.id : isCategory ? it.id : it.code;
              const label = it.label || it.name;
              const isSel = code === value;
              const hasInventory = !isCategory || window.PRODUCTS.some(p => (p.categories || [p.category || 'lights']).includes(it.id));
              const hasLooks = isBus && it.installPhotos && it.installPhotos.looks && it.installPhotos.looks.length > 1;
              return (
                <button
                  key={code}
                  className={"picker__option " + (isSel ? "is-selected" : "") + (hasInventory ? "" : " is-empty-cat")}
                  onClick={() => isBus ? pickBus(it) : (onSelect(code), onClose())}
                >
                  {isBus && (
                    <div
                      className="thumb"
                      style={{ backgroundImage: it.img ? `url(${it.img})` : 'none', background: it.img ? `url(${it.img}) center/cover` : 'var(--sb-bone)' }}
                    ></div>
                  )}
                  <div className="label">
                    <div>{label}{isCategory && !hasInventory && <span className="soon-tag">Coming soon</span>}{hasLooks && <span className="soon-tag" style={{background:'var(--sb-bone)',color:'var(--sb-mute)'}}>2 shapes</span>}</div>
                    <div className="code">{isBus ? it.short : isCategory ? '' : it.code}</div>
                  </div>
                </button>
              );
            })}
          </div>
        )}
      </div>
    </div>
  );
}

// ---- Selector — chat-style conversation ------------------------------
function SelectorChat({ categoryId, busId, stateCode, side, mountStyle, onOpen, onSetSide, onSetMount, geoConfident }) {
  const bus = window.BUSES.find(b => b.id === busId);
  const state = window.STATES.find(s => s.code === stateCode);
  const cat = window.CATEGORIES.find(c => c.id === categoryId);
  const needsExtras = categoryId === 'lights';
  const baseChosen = categoryId && busId && stateCode;
  const allChosen = baseChosen && (!needsExtras || (side && mountStyle));
  const matchOpts = needsExtras ? { side, mountStyle } : {};
  const matches = baseChosen ? window.matchProducts(busId, stateCode, categoryId, matchOpts) : [];

  // Resolve bus-specific install photos with sensible fallbacks
  const ip = (bus && bus.installPhotos) || {};
  const photoFor = (s, m) => window.busPhoto(bus, null, s, m, stateCode)
    || (s === 'front' ? bus && bus.frontImg : bus && bus.rearImg)
    || null;

  return (
    <div className="chat">
      <div className="chat__msg chat__msg--ai">
        <div className="chat__avatar">
          <img src="assets/logo/safebus-icon-yellow.png" alt="" />
        </div>
        <div className="chat__bubble">
          <div className="chat__name">SafeBus assistant <span className="chat__online"></span></div>
          <p className="chat__text">G'day. I'll find the right products for your bus in seconds. What are you after?</p>
          <div className="chat__chips">
            {window.CATEGORIES.map(c => {
              const hasInv = window.PRODUCTS.some(p => (p.categories || [p.category || 'lights']).includes(c.id));
              return (
                <button
                  key={c.id}
                  className={"chat__chip " + (categoryId === c.id ? "is-active" : "") + (hasInv ? "" : " is-soon")}
                  onClick={() => onOpen('__set_category_' + c.id)}
                >
                  {c.label}
                  {!hasInv && <span className="soon-tag">Soon</span>}
                </button>
              );
            })}
          </div>
        </div>
      </div>

      {categoryId && (
        <div className="chat__msg chat__msg--user">
          <div className="chat__bubble chat__bubble--user">
            {cat.label.charAt(0).toUpperCase() + cat.label.slice(1)} please.
          </div>
        </div>
      )}

      {categoryId && (
        <div className="chat__msg chat__msg--ai">
          <div className="chat__avatar"><img src="assets/logo/safebus-icon-yellow.png" alt="" /></div>
          <div className="chat__bubble">
            <p className="chat__text">Great. Which bus?</p>
            {busId ? (
              <button className="chat__answer" onClick={() => onOpen('bus')}>
                <span className="chat__answer-thumb" style={{ backgroundImage: bus.img ? `url(${bus.img})` : 'none' }}></span>
                <span>{bus.name}</span>
                <span className="chat__answer-edit">change</span>
              </button>
            ) : (
              <button className="chat__cta" onClick={() => onOpen('bus')}>
                Pick a bus
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M5 12h14M13 5l7 7-7 7"/></svg>
              </button>
            )}
          </div>
        </div>
      )}

      {busId && (
        <div className="chat__msg chat__msg--ai">
          <div className="chat__avatar"><img src="assets/logo/safebus-icon-yellow.png" alt="" /></div>
          <div className="chat__bubble">
            <p className="chat__text">
              And where are you registered?{" "}
              {geoConfident === false && stateCode && <span className="chat__sub">(I think you're in {stateCode} — tell me if I'm wrong.)</span>}
            </p>
            {stateCode ? (
              <button className="chat__answer" onClick={() => onOpen('state')}>
                <span className="chat__answer-flag">{stateCode}</span>
                <span>{state.name}</span>
                <span className="chat__answer-edit">change</span>
              </button>
            ) : (
              <button className="chat__cta" onClick={() => onOpen('state')}>
                Pick a state
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M5 12h14M13 5l7 7-7 7"/></svg>
              </button>
            )}
          </div>
        </div>
      )}

      {baseChosen && needsExtras && (
        <div className="chat__msg chat__msg--ai">
          <div className="chat__avatar"><img src="assets/logo/safebus-icon-yellow.png" alt="" /></div>
          <div className="chat__bubble">
            <p className="chat__text">Front or rear of the bus?</p>
            <div className="chat__photo-options">
              {['front','rear'].map(s => (
                <button
                  key={s}
                  className={"chat__photo-opt " + (side === s ? "is-active" : "")}
                  onClick={() => onSetSide(s)}
                >
                  <span
                    className="chat__photo-opt-img"
                    style={{ backgroundImage: `url(${photoFor(s, 'external')})` }}
                  ></span>
                  <span className="chat__photo-opt-label">{s.charAt(0).toUpperCase() + s.slice(1)}</span>
                </button>
              ))}
            </div>
          </div>
        </div>
      )}

      {baseChosen && needsExtras && side && (
        <div className="chat__msg chat__msg--user">
          <div className="chat__bubble chat__bubble--user">
            {side === 'front' ? 'Front lights, please.' : 'Rear lights, please.'}
          </div>
        </div>
      )}

      {baseChosen && needsExtras && side && (
        <div className="chat__msg chat__msg--ai">
          <div className="chat__avatar"><img src="assets/logo/safebus-icon-yellow.png" alt="" /></div>
          <div className="chat__bubble">
            <p className="chat__text">Internally or externally mounted?</p>
            <div className="chat__photo-options">
              {['internal','external'].map(m => (
                <button
                  key={m}
                  className={"chat__photo-opt " + (mountStyle === m ? "is-active" : "")}
                  onClick={() => onSetMount(m)}
                >
                  <span
                    className="chat__photo-opt-img"
                    style={{ backgroundImage: `url(${photoFor(side, m)})` }}
                  ></span>
                  <span className="chat__photo-opt-label">
                    {m === 'internal' ? 'Internal' : 'External'}
                    <span className="chat__photo-opt-sub">
                      {m === 'internal' ? 'Inside the window' : 'On the body'}
                    </span>
                  </span>
                </button>
              ))}
            </div>
          </div>
        </div>
      )}

      {baseChosen && needsExtras && mountStyle && (
        <div className="chat__msg chat__msg--user">
          <div className="chat__bubble chat__bubble--user">
            {mountStyle === 'internal' ? 'Internal mount.' : 'External mount.'}
          </div>
        </div>
      )}

      {allChosen && (
        <div className="chat__msg chat__msg--ai">
          <div className="chat__avatar"><img src="assets/logo/safebus-icon-yellow.png" alt="" /></div>
          <div className="chat__bubble chat__bubble--final">
            <p className="chat__text">
              Found <strong>{matches.length} match{matches.length === 1 ? '' : 'es'}</strong> for your{" "}
              <strong>{bus.short}</strong> in <strong>{stateCode}</strong>.
            </p>
            <a className="chat__cta chat__cta--accent" href="#results">
              Show me
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M12 5v14M5 13l7 7 7-7"/></svg>
            </a>
          </div>
        </div>
      )}
    </div>
  );
}

// ---- Selector — command palette / spotlight ---------------------------
function SelectorCommand({ categoryId, busId, stateCode, onOpen, geoConfident }) {
  const bus = window.BUSES.find(b => b.id === busId);
  const state = window.STATES.find(s => s.code === stateCode);
  const cat = window.CATEGORIES.find(c => c.id === categoryId);
  const allChosen = categoryId && busId && stateCode;
  const matchCount = allChosen ? window.matchProducts(busId, stateCode, categoryId).length : 0;

  return (
    <div className="cmd">
      <div className="cmd__bar">
        <div className="cmd__prompt">
          <span className="cmd__caret">{"➜"}</span>
          <span className="cmd__phrase">Find</span>
        </div>
        <button className={"cmd__token " + (categoryId ? "is-set" : "is-empty")} onClick={() => onOpen('category')}>
          <span className="cmd__token-key">type</span>
          <span className="cmd__token-val">{cat ? cat.label : 'products…'}</span>
        </button>
        <button className={"cmd__token " + (busId ? "is-set" : "is-empty")} onClick={() => onOpen('bus')}>
          <span className="cmd__token-key">bus</span>
          <span className="cmd__token-val">
            {bus && <span className="cmd__token-thumb" style={{ backgroundImage: bus.img ? `url(${bus.img})` : 'none' }}></span>}
            {bus ? bus.short : 'any…'}
          </span>
        </button>
        <button className={"cmd__token " + (stateCode ? "is-set" : "is-empty")} onClick={() => onOpen('state')}>
          <span className="cmd__token-key">state</span>
          <span className="cmd__token-val">{stateCode || 'any…'}</span>
        </button>
        <div className="cmd__spacer"></div>
        <kbd className="cmd__kbd">⏎</kbd>
      </div>
      <div className="cmd__hint">
        {!categoryId && <><span className="cmd__hint-dot"></span>Start by picking what you're after</>}
        {categoryId && !busId && <><span className="cmd__hint-dot"></span>Tell us your bus model</>}
        {categoryId && busId && !stateCode && <><span className="cmd__hint-dot"></span>One more — your state</>}
        {allChosen && (
          <>
            <span className="cmd__hint-dot is-ready"></span>
            Ready — <strong>{matchCount} match{matchCount === 1 ? '' : 'es'}</strong> for your {bus.short} in {stateCode}
          </>
        )}
        {geoConfident === false && stateCode && !allChosen && (
          <button className="cmd__hint-link" onClick={() => onOpen('state')}>
            (state guessed from timezone — change)
          </button>
        )}
      </div>
    </div>
  );
}

// ---- Selector — inline sentence (default) ------------------------------
function SelectorInline({ categoryId, busId, stateCode, look, side, mountStyle, rearMountStyle, onOpen, onSetSide, onSetMount, geoConfident }) {
  const bus = window.BUSES.find(b => b.id === busId);
  const state = window.STATES.find(s => s.code === stateCode);
  const cat = window.CATEGORIES.find(c => c.id === categoryId);
  const needsExtras = categoryId === 'lights';
  const baseChosen = categoryId && busId && stateCode;
  // The rich visual workflow only runs in supported states.
  const photoState = stateCode && window.PHOTO_LIGHTS_STATES.includes(stateCode);
  const showFollowup = needsExtras && baseChosen && photoState;
  const photoFor = (s, m) => window.busPhoto(bus, look, s, m, stateCode);
  const matchCount = baseChosen
    ? window.matchProducts(busId, stateCode, categoryId,
        categoryId === 'lights' ? { side, mountStyle }
        : categoryId === 'kit' ? { side: 'both', kitMount: window.kitMountKey(mountStyle, rearMountStyle) }
        : {}).length
    : 0;
  return (
    <div className="selector-card">
      <div className="selector-card__eyebrow">
        <span className="pulse"></span>
        <span className="t-eyebrow">SafeBus assistant</span>
      </div>
      <p className="selector-prompt">
        I'm looking for{" "}
        <button
          className={"blank " + (categoryId ? "" : "is-empty")}
          onClick={() => onOpen('category')}
          aria-label="Choose category"
        >
          {cat ? cat.label : "lights"}
        </button>
        {" "}for{" "}
        <button
          className={"blank " + (busId ? "" : "is-empty")}
          onClick={() => onOpen('bus')}
          aria-label="Choose bus"
        >
          {bus ? (bus.sentenceName || bus.short) : "my bus"}
        </button>
        ,<br />and I'm in{" "}
        <button
          className={"blank " + (stateCode ? "" : "is-empty")}
          onClick={() => onOpen('state')}
          aria-label="Choose state"
        >
          {state ? state.code : "my state"}
        </button>
        .
      </p>

      {/* Lights workflow. Side (front/rear/both) and mount are normally chosen in
          the bus picker dialog. The inline steps below are only a fallback for
          when the bus was picked before a photo-state was set (so the dialog
          didn't ask). */}
      {showFollowup && (!side || !mountStyle) && (
        <div className="followup">
          {!side && (
            <div className="followup__step">
              <span className="followup__q">Lights for the front, rear or both?</span>
              <div className="followup__opts followup__opts--three">
                {[
                  { id: 'front', label: 'Front' },
                  { id: 'rear',  label: 'Rear' },
                  { id: 'both',  label: 'Both' },
                ].map(o => (
                  <button
                    key={o.id}
                    className={"followup__opt " + (side === o.id ? "is-active" : "")}
                    onClick={() => onSetSide(side === o.id ? null : o.id)}
                  >
                    <span className="followup__opt-img" style={{ backgroundImage: `url(${photoFor(o.id, 'nolights')})` }}></span>
                    <span className="followup__opt-label">{o.label}</span>
                  </button>
                ))}
              </div>
            </div>
          )}

          {side && !mountStyle && (
            <div className="followup__step">
              <span className="followup__q">Internal or external mount?</span>
              <div className="followup__opts">
                {['internal','external'].map(m => (
                  <button
                    key={m}
                    className={"followup__opt " + (mountStyle === m ? "is-active" : "")}
                    onClick={() => onSetMount(mountStyle === m ? null : m)}
                  >
                    <span className="followup__opt-img" style={{ backgroundImage: `url(${photoFor(side, m)})` }}></span>
                    <span className="followup__opt-label">
                      {m === 'internal' ? 'Internal' : 'External'}
                      <span className="followup__opt-sub">{m === 'internal' ? 'Inside the window' : 'On the body'}</span>
                    </span>
                  </button>
                ))}
              </div>
            </div>
          )}
        </div>
      )}

      <div className="selector-meta">
        {geoConfident === false && stateCode && (
          <span className="pill">
            Guessed from timezone
            <button className="link" onClick={() => onOpen('state')}>Not {stateCode}?</button>
          </span>
        )}
        {baseChosen && (
          <span className="pill" style={{background: 'var(--sb-yellow)', borderColor: 'var(--sb-black)', color: 'var(--sb-black)'}}>
            ↓ {matchCount} match{matchCount === 1 ? '' : 'es'}
          </span>
        )}
      </div>
    </div>
  );
}

// (Removed: Stepper and Compact selectors — superseded by Chat and Command styles)

// ---- Product Card ------------------------------------------------------
function ProductCard({ p, style, onAdd, isAdded }) {
  const [photoIdx, setPhotoIdx] = useState(0);
  const hero = p.photos && p.photos[photoIdx];
  const placementLabel = { front: 'FRONT', rear: 'REAR', both: 'FRONT + REAR' }[p.placement];
  // Pull a category label for the chip
  const catLabel = (() => {
    if (p.isKit) return 'Kit';
    const c = (p.categories || [])[0];
    const found = window.CATEGORIES.find(x => x.id === c);
    return found ? found.label.replace(/^./, m => m.toUpperCase()) : 'Product';
  })();
  // A short "for X" trailing phrase derived from the bus list (if specific)
  const busHint = (() => {
    if (!p.buses || p.buses.length === 0 || p.buses.length > 4) return null;
    const names = p.buses.map(id => (window.BUSES.find(b => b.id === id) || {}).short).filter(Boolean);
    return names.length ? names.join(', ') : null;
  })();
  return (
    <article className="product" data-style={style}>
      <a
        className="product__media product__media--link"
        href={p.permalink}
        target="_blank"
        rel="noopener"
        onClick={(e) => {
          // Click-to-lightbox is now opt-in via the dedicated button.
          // Whole media area links to the WP product page.
        }}
      >
        {hero ? (
          <img src={hero} alt={p.name} loading="lazy" />
        ) : (
          <div className="placeholder">
            <svg viewBox="0 0 24 24" fill="none" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="8.5" cy="10.5" r="1.5"/><path d="m3 17 5-4 4 3 4-5 5 6"/></svg>
            <span>Photo coming soon · {p.sku}</span>
          </div>
        )}
        {placementLabel && <span className="placement">{placementLabel}</span>}
        {p.photos && p.photos.length > 1 && (
          <div
            className="photo-dots"
            onClick={e => { e.preventDefault(); e.stopPropagation(); }}
          >
            {p.photos.slice(0, 6).map((_, i) => (
              <button
                key={i}
                className={"photo-dot " + (i === photoIdx ? "is-active" : "")}
                onClick={(e) => { e.preventDefault(); setPhotoIdx(i); }}
                aria-label={`Photo ${i+1}`}
              />
            ))}
          </div>
        )}
      </a>
      <div className="product__body">
        <div className="product__sku">
          {p.sku}{p.sku && ' · '}{catLabel}
          {p._kitVariants && p._kitVariants.length > 1 && (
            <span style={{marginLeft: 6, color: 'var(--sb-mute)'}}>
              · +{p._kitVariants.length - 1} state variant{p._kitVariants.length === 2 ? '' : 's'}
            </span>
          )}
        </div>
        <h3 className="product__name">
          <a href={p.permalink} target="_blank" rel="noopener" style={{color: 'inherit', textDecoration: 'none'}}>
            {p.name}
          </a>
        </h3>
        {busHint && <div className="product__family">For {busHint}</div>}

        {style === 'showcase' && (
          <div className="showcase__tags">
            {placementLabel && <span className="tag is-accent">{placementLabel}</span>}
            {p.isKit && <span className="tag">Complete kit</span>}
            {(p.states || []).length > 0 && (p.states.length <= 2
              ? p.states.map(s => <span key={s} className="tag">{s}</span>)
              : p.states.length < 8 && <span className="tag">{p.states.length} states</span>
            )}
            {p.states && p.states.length === 8 && <span className="tag">All states</span>}
          </div>
        )}

        {style === 'spec' && (
          <div style={{marginBottom: 14}}>
            <div className="specrow"><span>Placement</span><span>{placementLabel || '—'}</span></div>
            <div className="specrow"><span>States</span><span>{p.states && p.states.length === 8 ? 'All' : (p.states || []).join(', ')}</span></div>
            <div className="specrow"><span>Buses</span><span>{busHint || `${(p.buses||[]).length} models`}</span></div>
            <div className="specrow"><span>Type</span><span>{catLabel}</span></div>
          </div>
        )}

        <div className="product__foot">
          <div
            className="product__price"
            dangerouslySetInnerHTML={{ __html: p.priceHtml || (p.price ? `$${p.price}` : '') }}
          />
          <a
            className="btn-cart"
            href={p.permalink}
            target="_blank"
            rel="noopener"
            onClick={() => onAdd && onAdd(p.sku)}
          >
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M5 12h14M13 5l7 7-7 7"/></svg>
            View on store
          </a>
        </div>
      </div>
    </article>
  );
}

// ---- Compliance strip --------------------------------------------------
function ComplianceStrip() {
  const stats = [
    { num: '1900', unit: 'cd', label: 'Peak brightness from our flood-light LED core module' },
    { num: '12–24', unit: 'V', label: 'Works on every 12V or 24V Australian bus electrical system' },
    { num: '8', unit: 'states', label: 'Compliance covered across every Australian state and territory' },
    { num: 'TS-150', unit: '', label: 'NSW-compliant variants with the regulation 70mm black surround' },
  ];
  return (
    <section className="section compliance" id="compliance">
      <div className="section__inner">
        <div className="results__head" style={{marginBottom: 12}}>
          <div>
            <div className="t-eyebrow" style={{marginBottom: 8}}>100 series</div>
            <h2 className="results__title">Built for every Australian school bus.</h2>
          </div>
        </div>
        <p style={{maxWidth: '60ch', color: 'var(--sb-mute)', marginBottom: 36}}>
          A comprehensive selection of school-bus warning lights designed to meet the requirements of all Australian states and territories. Universal and custom-made fitting options for every common chassis in the Australian market.
        </p>
        <div className="compliance__grid">
          {stats.map((s, i) => (
            <div className="stat" key={i}>
              <div className="stat__num">{s.num}<small> {s.unit}</small></div>
              <div className="stat__lbl">{s.label}</div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

// ---- Footer ------------------------------------------------------------
function Footer() {
  return (
    <footer className="foot">
      <div className="foot__inner">
        <div className="foot__top">
          <div>
            <div className="foot__brand">
              <img className="brand__logo foot__logo" src="assets/logo/safebus-logo.png" alt="SafeBus" />
            </div>
            <p className="foot__desc">School-bus warning lights, engineered in Australia. Compliant in every state and territory — from NSW TS-150 to national fitments.</p>
          </div>
          <div className="foot__col">
            <h4>Products</h4>
            <a href="#">Surface mount</a>
            <a href="#">Window mount</a>
            <a href="#">Flush mount</a>
            <a href="#">Retrofit</a>
            <a href="#">Roof mount</a>
            <a href="#">Destination board</a>
          </div>
          <div className="foot__col">
            <h4>Help</h4>
            <a href="#">Installation guides</a>
            <a href="#">Find a fitter</a>
            <a href="#">Warranty</a>
            <a href="#">Returns</a>
          </div>
          <div className="foot__col">
            <h4>Contact</h4>
            <a href="tel:1300391848">1300 391 848</a>
            <a href="mailto:sales@safebus.com.au">sales@safebus.com.au</a>
            <a href="#">Trade enquiries</a>
          </div>
        </div>
        <div className="foot__bot">
          <span>© 2026 SafeBus Pty Ltd</span>
          <span>ABN · ADR · TS-150 compliant</span>
        </div>
      </div>
    </footer>
  );
}

// ---- Cart toast --------------------------------------------------------
function Toast({ msg, onDone }) {
  useEffect(() => {
    if (!msg) return;
    const t = setTimeout(onDone, 1800);
    return () => clearTimeout(t);
  }, [msg, onDone]);
  if (!msg) return null;
  return <div className="toast">✓ {msg}</div>;
}

// ---- Cycling word in the hero headline --------------------------------
const CYCLE_WORDS = ['light', 'light kit', 'sticker', 'CCTV', 'child check'];
function CycleWord() {
  const [i, setI] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setI(v => (v + 1) % CYCLE_WORDS.length), 2000);
    return () => clearInterval(id);
  }, []);
  return (
    <span className="cycle-word" aria-live="polite">
      {CYCLE_WORDS.map((w, idx) => (
        <span key={w} className={"cycle-word__item " + (idx === i ? "is-in" : "")}>
          {w}
        </span>
      ))}
      <span className="cycle-word__measure" aria-hidden="true">
        {CYCLE_WORDS.reduce((a, b) => b.length > a.length ? b : a)}
      </span>
    </span>
  );
}

// ---- Main App ----------------------------------------------------------
const PALETTE_OPTIONS = [
  { id: 'classic', colors: ['#FFC72C', '#0A0A0A', '#F5F2EA'] },
  { id: 'hivis',   colors: ['#F5D300', '#FF4D00', '#0A0A0A'] },
  { id: 'mono',    colors: ['#0A0A0A', '#F2F1ED', '#FFC72C'] },
  { id: 'night',   colors: ['#0B0C0E', '#FFC72C', '#FF8A00'] },
];

function App() {
  const [t, setT] = useTweaks(TWEAK_DEFAULTS);

  // Push kit-display strategy into the global so matchProducts() picks it up
  // everywhere (selectors, results, count pills).
  useEffect(() => {
    window.__kitStrategy = t.kitStrategy || 'all';
  }, [t.kitStrategy]);

  // Apply palette
  useEffect(() => {
    document.documentElement.setAttribute('data-palette', t.palette);
  }, [t.palette]);

  // Apply theme (signature / modern / editorial)
  useEffect(() => {
    document.documentElement.setAttribute('data-theme', t.theme || 'signature');
  }, [t.theme]);

  // Selector state — geo-guess default
  const initialGuess = useMemo(() => window.guessState(), []);
  const [stateCode, setStateCode] = useState(initialGuess.code);
  const [busId, setBusId] = useState(null);
  const [categoryId, setCategoryId] = useState('lights'); // sensible default — every existing SKU is in 'lights'
  const [picker, setPicker] = useState(null); // 'bus' | 'state' | 'category' | null
  const [cart, setCart] = useState([]);
  const [toast, setToast] = useState(null);
  const [geoConfident, setGeoConfident] = useState(initialGuess.confident);
  const [side, setSide] = useState(null);        // 'front' | 'rear' (lights only)
  const [mountStyle, setMountStyle] = useState(null); // 'internal' | 'external' (lights only)
  const [rearMountStyle, setRearMountStyle] = useState(null); // rear mount when side==='both' & differs
  const [showAlso, setShowAlso] = useState(true); // "Also compatible" section expanded
  // Curate mode (hidden admin). Opens via #curate hash or the Tweaks button.
  const [curateOpen, setCurateOpen] = useState(() => (typeof location !== 'undefined' && location.hash.indexOf('curate') >= 0));
  const [curationVersion, setCurationVersion] = useState(0);
  useEffect(() => {
    const onHash = () => setCurateOpen(location.hash.indexOf('curate') >= 0);
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);
  const [look, setLook] = useState(null);        // 'new' | 'old' (Rosa/Coaster look)

  // Reset the full lights sub-flow when the category changes (a different product
  // type). Body shape, side and mount are chosen inside the bus picker and set
  // alongside busId, so we must NOT clear them on busId/side change or we'd
  // clobber the dialog's choices.
  useEffect(() => { setLook(null); setSide(null); setMountStyle(null); setRearMountStyle(null); }, [categoryId]);

  // Inline fallback: picking a side clears any stale mount choice.
  const chooseSide = useCallback((v) => { setSide(v); setMountStyle(null); setRearMountStyle(null); }, []);

  const addToCart = useCallback((sku) => {
    setCart(c => c.includes(sku) ? c : [...c, sku]);
    setToast(`Added ${sku} to cart`);
  }, []);

  const matches = useMemo(() => {
    if (!busId || !stateCode || !categoryId) return [];
    let opts = {};
    if (categoryId === 'lights') {
      const mountForFilter = (side === 'both' && rearMountStyle && rearMountStyle !== mountStyle)
        ? null : mountStyle;
      opts = { side, mountStyle: mountForFilter };
    } else if (categoryId === 'kit') {
      // Kits: front mount = mountStyle, rear mount = rearMountStyle → kit variant.
      opts = { side: 'both', kitMount: window.kitMountKey(mountStyle, rearMountStyle) };
    }
    return window.matchProducts(busId, stateCode, categoryId, opts);
    // t.kitStrategy is read inside matchProducts via window.__kitStrategy
  }, [busId, stateCode, categoryId, side, mountStyle, rearMountStyle, t.kitStrategy, curationVersion]);

  // Smooth scroll: for the lights flow, the user still has front/rear + mount
  // Robust scroll helper. Uses setTimeout-based retries (NOT requestAnimationFrame,
  // which is paused when the tab is backgrounded). Polls a few times so the scroll
  // survives the modal unmount + any late re-render, and re-fires if the target
  // moved. Plain timers fire regardless of tab visibility.
  // Scroll the given element into view. Uses an instant scroll inside a short
  // setTimeout (both fire reliably regardless of tab visibility, unlike
  // requestAnimationFrame / behavior:'smooth' which are paused in background
  // tabs). Retries a few times in case the target hasn't rendered yet.
  const scrollToEl = useCallback((getEl, offset) => {
    let tries = 0;
    const attempt = () => {
      const el = getEl();
      if (el) {
        const top = Math.max(0, el.getBoundingClientRect().top + window.scrollY - offset);
        window.scrollTo(0, top);
      } else if (tries < 6) {
        tries++;
        setTimeout(attempt, 80);
      }
    };
    setTimeout(attempt, 60);
  }, []);

  // Scroll behaviour for the lights flow. The visual follow-up only exists in
  // supported states (PHOTO_LIGHTS_STATES); elsewhere we go straight to results.
  const photoFlow = categoryId === 'lights' && stateCode && window.PHOTO_LIGHTS_STATES.includes(stateCode);

  // bus + state chosen → scroll to the follow-up (front/rear) for the
  // photo flow, otherwise jump straight to results.
  useEffect(() => {
    if (!busId || !stateCode) return;
    if (photoFlow) {
      scrollToEl(() => document.querySelector('.followup'), 90);
    } else {
      scrollToEl(() => document.getElementById('results'), 70);
    }
  }, [busId, stateCode, categoryId, photoFlow, scrollToEl]);

  // After picking front/rear, glide down to the mount step (only renders once side is set).
  useEffect(() => {
    if (photoFlow && busId && stateCode && side && !mountStyle) {
      scrollToEl(() => {
        const steps = document.querySelectorAll('.followup__step');
        return steps[steps.length - 1];
      }, 90);
    }
  }, [side, mountStyle, photoFlow, busId, stateCode, scrollToEl]);

  // Once the lights flow is fully specified (side + mount), glide to results.
  useEffect(() => {
    if (photoFlow && busId && stateCode && side && mountStyle) {
      scrollToEl(() => document.getElementById('results'), 70);
    }
  }, [side, mountStyle, photoFlow, busId, stateCode, scrollToEl]);

  const heroLayout = t.heroLayout || 'inline';

  return (
    <React.Fragment>
      <Header cartCount={cart.length} />

      {/* HERO */}
      <section className="hero">
        <div className="hero__inner">
          <div className="hero__crumb">
            <span className="chip"><span className="dot"></span>100 Series · LED School-Bus Lights</span>
            <span className="chip">12V – 24V</span>
            <span className="chip">All states</span>
          </div>
          <h1 className="hero__title">
            The right <CycleWord /><br />for your school bus.<br />
            <em>In 60 seconds.</em>
          </h1>
          <p className="hero__sub">
            SafeBus 100 series school-bus warning lights — engineered for every common Australian chassis and every state's compliance regime. Tell us what you drive and where you're registered.
          </p>

          {heroLayout === 'inline' && (
            <SelectorInline
              categoryId={categoryId}
              busId={busId}
              stateCode={stateCode}
              geoConfident={geoConfident}
              look={look}
              side={side}
              mountStyle={mountStyle}
              rearMountStyle={rearMountStyle}
              onOpen={setPicker}
              onSetSide={chooseSide}
              onSetMount={setMountStyle}
            />
          )}
          {heroLayout === 'chat' && (
            <SelectorChat
              categoryId={categoryId}
              busId={busId}
              stateCode={stateCode}
              side={side}
              mountStyle={mountStyle}
              geoConfident={geoConfident}
              onSetSide={chooseSide}
              onSetMount={setMountStyle}
              onOpen={(arg) => {
                if (typeof arg === 'string' && arg.startsWith('__set_category_')) {
                  setCategoryId(arg.replace('__set_category_', ''));
                } else {
                  setPicker(arg);
                }
              }}
            />
          )}
          {heroLayout === 'command' && (
            <SelectorCommand
              categoryId={categoryId}
              busId={busId}
              stateCode={stateCode}
              geoConfident={geoConfident}
              onOpen={setPicker}
            />
          )}

          {geoConfident === false && stateCode && heroLayout !== 'inline' && (
            <div className="geo-hint">
              <span>🛰</span>
              Detected: {stateCode}
              <button className="change" onClick={() => setPicker('state')}>change</button>
            </div>
          )}
        </div>
      </section>

      {/* RESULTS */}
      <section className="section results" id="results">
        <div className="section__inner">
          <div className="results__head">
            <div>
              <div className="t-eyebrow" style={{marginBottom: 8}}>Matched products</div>
              <h2 className="results__title">
                {busId && stateCode ? (() => {
                  const plural = (window.CATEGORIES.find(c=>c.id===categoryId) || {pluralLabel:'lights'}).pluralLabel;
                  let head = plural.charAt(0).toUpperCase() + plural.slice(1);
                  if (categoryId === 'lights' && (side || mountStyle)) {
                    if (side === 'both') {
                      const fm = mountStyle, rm = rearMountStyle || mountStyle;
                      head = (fm && rm && fm === rm)
                        ? (fm === 'internal' ? 'Internal' : 'External') + ' front & rear lights'
                        : 'Front & rear lights';
                    } else {
                      const bits = [mountStyle && (mountStyle === 'internal' ? 'internal' : 'external'), side].filter(Boolean).join(' ');
                      head = bits.charAt(0).toUpperCase() + bits.slice(1) + ' lights';
                    }
                  }
                  return `${head} for your ${window.BUSES.find(b=>b.id===busId).short} in ${stateCode}`;
                })() : "Pick a bus & state to see matches"}
              </h2>
              {busId && stateCode && (() => {
                const bus = window.BUSES.find(b => b.id === busId);
                const st = window.STATES.find(s => s.code === stateCode);
                const looks = bus && bus.installPhotos && bus.installPhotos.looks;
                const lookLabel = look && looks && (looks.find(l => l.id === look) || {}).label;
                const sideLabel = { front: 'Front', rear: 'Rear', both: 'Front & rear' }[side];
                const mLabel = m => m === 'internal' ? 'Internal mount' : m === 'external' ? 'External mount' : null;
                const lightsLike = categoryId === 'lights' || categoryId === 'kit';
                const cat = window.CATEGORIES.find(c => c.id === categoryId);
                const chips = [];
                if (cat) chips.push({ k: 'Type', v: cat.label.replace(/^./, c => c.toUpperCase()) });
                if (bus) chips.push({ k: 'Bus', v: bus.short + (lookLabel ? ` · ${lookLabel}` : '') });
                if (st) chips.push({ k: 'State', v: `${st.code} — ${st.name}` });
                if (categoryId === 'lights' && sideLabel) chips.push({ k: 'Fitment', v: sideLabel });
                if (lightsLike && mountStyle) {
                  if (side === 'both' && rearMountStyle && rearMountStyle !== mountStyle) {
                    chips.push({ k: 'Front', v: mLabel(mountStyle) });
                    chips.push({ k: 'Rear', v: mLabel(rearMountStyle) });
                  } else {
                    chips.push({ k: 'Mount', v: mLabel(mountStyle) });
                  }
                }
                return (
                  <div className="results__chosen">
                    {chips.map((c, i) => (
                      <span key={i} className="chosen-chip">
                        <span className="chosen-chip__k">{c.k}</span>
                        <span className="chosen-chip__v">{c.v}</span>
                      </span>
                    ))}
                    <button className="chosen-chip chosen-chip--edit" onClick={() => setPicker('bus')}>Change</button>
                  </div>
                );
              })()}
            </div>
            {busId && stateCode && (
              <div className="results__count">{matches.length} match{matches.length===1?'':'es'}</div>
            )}
          </div>

          {!busId || !stateCode ? (
            <div className="no-results">
              <div className="no-results__icon">
                <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="8"/><path d="M12 8v4l3 2"/></svg>
              </div>
              <h3>Almost there.</h3>
              <p>Pick your bus model and state above and we'll show the SafeBus products that fit and comply with your local regulations.</p>
              <button className="link-btn" onClick={() => setPicker('bus')}>Pick a bus →</button>
            </div>
          ) : !window.PRODUCTS.some(p => (p.categories || [p.category || 'lights']).includes(categoryId)) ? (
            <div className="no-results">
              <div className="no-results__icon">
                <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/></svg>
              </div>
              <h3>{(window.CATEGORIES.find(c=>c.id===categoryId)||{}).pluralLabel || 'This category'} — coming soon.</h3>
              <p>We're expanding the SafeBus catalogue. Drop your details and we'll let you know the moment this product is ready to ship.</p>
              <a className="link-btn" href="mailto:sales@safebus.com.au?subject=Interest%20in%20new%20SafeBus%20products">Email sales →</a>
            </div>
          ) : matches.length === 0 ? (
            <div className="no-results">
              <div className="no-results__icon">
                <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 9v4M12 17h0M10.3 4.7L2.7 17a2 2 0 0 0 1.7 3h15.2a2 2 0 0 0 1.7-3L13.7 4.7a2 2 0 0 0-3.4 0Z"/></svg>
              </div>
              <h3>No standard match.</h3>
              <p>We don't have an off-the-shelf fitment for that combination — but we do plenty of custom work. Call us on 1300 391 848 or email sales@safebus.com.au.</p>
              <a className="link-btn" href="tel:1300391848">Call 1300 391 848 →</a>
            </div>
          ) : (
            (() => {
              const exact = matches.filter(p => p.compat !== 'also');
              const also  = matches.filter(p => p.compat === 'also');
              const renderCard = p => (
                <ProductCard
                  key={p.sku}
                  p={p}
                  style={t.cardStyle}
                  isAdded={cart.includes(p.sku)}
                  onAdd={addToCart}
                />
              );
              return (
                <React.Fragment>
                  {exact.length > 0 && (
                    <div className="results__grid">{exact.map(renderCard)}</div>
                  )}

                  {exact.length === 0 && also.length > 0 && (
                    <p className="results__empty-note">
                      No products are tagged specifically to your {window.BUSES.find(b=>b.id===busId).short} yet —
                      but these universal options fit it:
                    </p>
                  )}

                  {also.length > 0 && (
                    <div className="results__also">
                      <button
                        className="results__also-head"
                        onClick={() => setShowAlso(s => !s)}
                        aria-expanded={showAlso}
                      >
                        <span className="results__also-title">
                          Also compatible
                          <span className="results__also-count">{also.length}</span>
                        </span>
                        <span className="results__also-sub">
                          Universal parts &amp; accessories that fit any bus
                        </span>
                        <svg className={"results__also-chev " + (showAlso ? "is-open" : "")} width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M6 9l6 6 6-6"/></svg>
                      </button>
                      {showAlso && (
                        <div className="results__grid results__grid--also">{also.map(renderCard)}</div>
                      )}
                    </div>
                  )}
                </React.Fragment>
              );
            })()
          )}

          {busId && stateCode && matches.length > 0 && (
            <div className="note-box">
              <strong>Heads up on photo mapping:</strong> the "installed on real buses" thumbnails under each product are my best guess based on filenames + visible details (chassis, NSW surround, mount position). Some installs above could swap to a different SKU — let me know which photos belong to which product and I'll re-map.
            </div>
          )}
        </div>
      </section>

      <ComplianceStrip />
      <Footer />

      {picker && (
        <Picker
          kind={picker}
          value={picker === 'bus' ? busId : picker === 'category' ? categoryId : stateCode}
          stateCode={stateCode}
          style={t.busSelectorStyle}
          onClose={() => setPicker(null)}
          onSelect={(v, lk, sd, mt) => {
            if (picker === 'bus') {
              setBusId(v);
              setLook(lk || null);
              setSide(sd || null);          // chosen in the dialog (lights flow), else null
              if (sd === 'both' && mt && typeof mt === 'object') {
                setMountStyle(mt.front || null);
                setRearMountStyle(mt.rear || null);
              } else {
                setMountStyle(mt || null);  // single-side mount, else null
                setRearMountStyle(null);
              }
            }
            else if (picker === 'category') setCategoryId(v);
            else { setStateCode(v); setGeoConfident(true); }
          }}
          askSide={categoryId === 'lights' && !!stateCode && window.PHOTO_LIGHTS_STATES.includes(stateCode)}
          askMount={categoryId === 'kit' && !!stateCode && window.PHOTO_LIGHTS_STATES.includes(stateCode)}
          bothMountLayout={t.bothMountLayout}
        />
      )}
      <Toast msg={toast} onDone={() => setToast(null)} />

      {/* Tweaks panel */}
      <TweaksPanel>
        <TweakSection label="Visual theme">
          <TweakRadio
            label="Style"
            value={t.theme || 'signature'}
            onChange={v => setT('theme', v)}
            options={[
              { value: 'signature', label: 'Signature' },
              { value: 'modern',    label: 'Modern' },
              { value: 'editorial', label: 'Editorial' },
            ]}
          />
        </TweakSection>

        <TweakSection label="Hero layout">
          <TweakRadio
            label="Style"
            value={t.heroLayout}
            onChange={v => setT('heroLayout', v)}
            options={[
              { value: 'inline',  label: 'AI sentence' },
              { value: 'chat',    label: 'Chat' },
              { value: 'command', label: 'Command' },
            ]}
          />
        </TweakSection>

        <TweakSection label="Brand palette">
          <TweakColor
            label="Palette"
            value={(PALETTE_OPTIONS.find(p => p.id === t.palette) || PALETTE_OPTIONS[0]).colors}
            onChange={arr => {
              const found = PALETTE_OPTIONS.find(p => JSON.stringify(p.colors) === JSON.stringify(arr));
              if (found) setT('palette', found.id);
            }}
            options={PALETTE_OPTIONS.map(p => p.colors)}
          />
        </TweakSection>

        <TweakSection label="Product cards">
          <TweakRadio
            label="Style"
            value={t.cardStyle}
            onChange={v => setT('cardStyle', v)}
            options={[
              { value: 'showcase', label: 'Premium' },
              { value: 'studio',   label: 'Studio' },
              { value: 'default',  label: 'Editor' },
              { value: 'spec',     label: 'Spec' },
            ]}
          />
        </TweakSection>

        <TweakSection label="Bus picker">
          <TweakRadio
            label="Style"
            value={t.busSelectorStyle}
            onChange={v => setT('busSelectorStyle', v)}
            options={[
              { value: 'list', label: 'List' },
              { value: 'grid', label: 'Grid' },
            ]}
          />
        </TweakSection>

        <TweakSection label="“Both” mount step">
          <TweakRadio
            label="Layout"
            value={t.bothMountLayout || 'combined'}
            onChange={v => setT('bothMountLayout', v)}
            options={[
              { value: 'combined',   label: 'All 4' },
              { value: 'sequential', label: 'Front then rear' },
            ]}
          />
          <div style={{fontSize:11, color:'var(--sb-mute)', lineHeight:1.5, marginTop:-4}}>
            When the user picks <strong>Both</strong>: “All 4” shows front + rear mount photos on one dialog; “Front then rear” asks the front mount first, then the rear on a second dialog.
          </div>
        </TweakSection>

        <TweakSection label="Kit results strategy">
          <TweakSelect
            label="Show"
            value={t.kitStrategy || 'all'}
            onChange={v => setT('kitStrategy', v)}
            options={[
              { value: 'all',      label: '(a) All — every state-and-bus variant' },
              { value: 'collapse', label: '(b) Collapse — one card per bus / mount, state picked auto' },
              { value: 'top3',     label: '(c) Top 3 — cap at the three most relevant' },
            ]}
          />
          <div style={{fontSize:11, color:'var(--sb-mute)', lineHeight:1.5, marginTop:-4}}>
            (a) mirrors your current site. (b) is cleanest UX — one canonical product per bus/mount with state as a variant. (c) trims clutter when there are many matches.
          </div>
        </TweakSection>

        <TweakSection label="Admin">
          <TweakButton label="Curate products →" onClick={() => { location.hash = 'curate'; setCurateOpen(true); }} />
          <div style={{fontSize:11, color:'var(--sb-mute)', lineHeight:1.5, marginTop:6}}>
            Mark each product Exact / Also / Hide per bus. Overrides the automatic matching. Saves automatically; export the map when done.
          </div>
        </TweakSection>
      </TweaksPanel>

      {curateOpen && (
        <CuratePanel
          onClose={() => { setCurateOpen(false); if (location.hash.indexOf('curate') >= 0) location.hash = ''; }}
          onChange={() => setCurationVersion(v => v + 1)}
        />
      )}
    </React.Fragment>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
