/* A2 — Order fulfillment screen. Paid -> published -> delivered, top to bottom.
   Wired to the real API: download the photo, upload the sprite (and toy), publish
   the live game, preview it, then deliver (emails the customer). */
const Sord = window.SparkleClassicDesignSystem_7a1470;

function Section({ icon, title, done, children, tone }) {
  const { Card, Icon } = Sord;
  return (
    <Card surface="card" padding="lg">
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
        <span style={{ width: 30, height: 30, flex: '0 0 auto', display: 'flex', alignItems: 'center', justifyContent: 'center', background: done ? 'var(--success)' : tone || 'var(--cream)', color: done ? 'var(--cream)' : 'var(--plum-ink)', boxShadow: 'var(--ofx-edges)' }}>
          <Icon name={done ? 'check' : icon} size={16} />
        </span>
        <h2 className="spk-h3" style={{ fontSize: 18, margin: 0, whiteSpace: 'nowrap' }}>{title}</h2>
      </div>
      {children}
    </Card>
  );
}

/* Link slug editor. The server makes the final link unique (adds a fun word on a
   clash), so this just lets the worker tweak the name; no client-side taken-check. */
function SlugEditor({ slug, onChange }) {
  const { Icon, Input, Button } = Sord;
  const [editing, setEditing] = React.useState(false);
  const [val, setVal] = React.useState(slug);
  const norm = val.toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
  const save = () => { if (!norm) return; onChange(norm); setEditing(false); };
  return (
    <div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '10px 12px', background: 'var(--cream)', boxShadow: 'var(--ofx-edges)' }}>
        <Icon name="link" size={16} aria-hidden="true" />
        <span style={{ fontFamily: 'var(--font-pixel)', fontSize: 10, color: 'var(--plum-ink)', flex: 1, wordBreak: 'break-all' }}>sparkleclassic.com/<span style={{ color: 'var(--plum-accent)' }}>{slug}</span></span>
        {!editing && <button onClick={() => { setVal(slug); setEditing(true); }} style={{ background: 'none', border: 0, color: 'var(--plum-accent)', textDecoration: 'underline', cursor: 'pointer', fontSize: 12, fontWeight: 800 }}>Edit</button>}
      </div>
      {editing && (
        <div style={{ marginTop: 10 }}>
          <Input label="Link name" value={val} onChange={(e) => setVal(e.target.value)}
            hint={`Will be sparkleclassic.com/${norm || '…'} — if it is taken we add a fun word automatically.`} leftIcon={<Icon name="link" />} />
          <div style={{ display: 'flex', gap: 8, marginTop: 10 }}>
            <Button variant="primary" size="sm" onClick={save} disabled={!norm}>Use this name</Button>
            <Button variant="ghost" size="sm" onClick={() => setEditing(false)}>Cancel</Button>
          </div>
        </div>
      )}
    </div>
  );
}

/* File picker that reads the chosen image as a data URL and previews it. */
function SpriteUpload({ label, hint, value, onPick }) {
  const { Icon, Badge } = Sord;
  const ref = React.useRef(null);
  const pick = (f) => { if (!f) return; const r = new FileReader(); r.onload = () => onPick(r.result, f.name); r.readAsDataURL(f); };
  return (
    <div>
      {value ? (
        <div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
          <img src={value} alt="" style={{ width: 72, height: 72, objectFit: 'cover', imageRendering: 'pixelated', boxShadow: 'var(--ofx-edges)', background: 'var(--cream)' }} />
          <div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}><strong style={{ fontSize: 14, color: 'var(--plum-ink)' }}>{label} ready</strong><Badge tone="success" dot>loaded</Badge></div>
            <button onClick={() => ref.current && ref.current.click()} style={{ background: 'none', border: 0, color: 'var(--plum-accent)', textDecoration: 'underline', cursor: 'pointer', padding: 0, fontSize: 13, marginTop: 4 }}>Replace</button>
          </div>
        </div>
      ) : (
        <button onClick={() => ref.current && ref.current.click()} style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8, padding: '22px 16px', cursor: 'pointer', color: 'var(--plum-ink)', background: 'var(--pink-soft)', border: '3px dashed var(--plum-ink)' }}>
          <span style={{ fontSize: 28 }}><Icon name="image" /></span>
          <strong style={{ fontSize: 15 }}>{label}</strong>
          <span className="spk-muted" style={{ fontSize: 13 }}>{hint}</span>
        </button>
      )}
      <input ref={ref} type="file" accept="image/png,image/*" onChange={(e) => pick(e.target.files[0])} style={{ display: 'none' }} />
    </div>
  );
}

const EVENT_LABEL = {
  paid: 'Paid via Stripe', published: 'Game published', ready: 'Delivered, link emailed',
  photo_deleted: 'Photo deleted', email_failed: 'Email failed to send', refunded: 'Refunded via Stripe',
};
const EVENT_ICON = { paid: 'check', published: 'gamepad', ready: 'mail', photo_deleted: 'trash', email_failed: 'alert', refunded: 'check' };

function ActivityLog({ events }) {
  const { Icon } = Sord;
  const items = (events || []).map((e) => ({ icon: EVENT_ICON[e.event] || 'info', t: EVENT_LABEL[e.event] || e.event, when: fmtAgo(e.at) }));
  if (!items.length) return <p className="spk-muted" style={{ fontSize: 13, margin: 0 }}>No activity yet.</p>;
  return (
    <div style={{ display: 'grid', gap: 0 }}>
      {items.map((e, i) => (
        <div key={i} style={{ display: 'flex', gap: 10, alignItems: 'flex-start' }}>
          <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', flex: '0 0 auto' }}>
            <span style={{ width: 24, height: 24, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'var(--cream)', color: 'var(--plum-ink)', boxShadow: 'var(--ofx-edges)' }}><Icon name={e.icon} size={12} /></span>
            {i < items.length - 1 && <span style={{ width: 2, height: 14, background: 'var(--border-soft)' }} />}
          </div>
          <div style={{ paddingBottom: i < items.length - 1 ? 9 : 0 }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--plum-ink)' }}>{e.t}</div>
            <div className="spk-muted" style={{ fontSize: 12 }}>{e.when}</div>
          </div>
        </div>
      ))}
    </div>
  );
}

function OrderDetail({ order, events, role, onBack, onUpdate, wall: wallProp }) {
  const { Button, Card, Badge, Avatar, Icon, Dialog, Toast } = Sord;
  const [status, setStatus] = React.useState(order.status);
  const [slug, setSlug] = React.useState(order.slug);
  const [petType, setPetType] = React.useState(order.kind === 'Dog' ? 'dog' : 'cat');
  const [spriteScale, setSpriteScale] = React.useState(order.spriteScale || 1);
  const [spriteData, setSpriteData] = React.useState(null);
  const [toyData, setToyData] = React.useState(null);
  const [link, setLink] = React.useState(order.game.link || '');
  const [busy, setBusy] = React.useState(null);
  const [refund, setRefund] = React.useState(false);
  const [toast, setToast] = React.useState(null);
  const [wall, setWall] = React.useState(wallProp || null);
  const [photoUrl, setPhotoUrl] = React.useState(null); // pet photo as a data URL — thumbnail + download
  const [photoErr, setPhotoErr] = React.useState(null);

  const published = status === 'published' || status === 'ready' || !!order.game.published;
  const flash = (tone, title, msg) => { setToast({ tone, title, msg }); setTimeout(() => setToast(null), 3200); };
  // Below ~960px the content area (sidebar present down to 760px, then full width)
  // is too narrow for two columns — stack them so nothing overflows.
  const stack = useIsNarrow(960);

  // Load the pet photo once (while it's still kept) so we can show a thumbnail and
  // download without re-fetching. The server returns RAW base64, so we wrap it once.
  React.useEffect(() => {
    let alive = true;
    setPhotoUrl(null); setPhotoErr(null);
    if (order.photo.state !== 'present') return;
    apiFetch(`/api/order?id=${encodeURIComponent(order.id)}&photo=1`)
      .then((r) => { if (alive) setPhotoUrl(`data:${r.mime || 'image/jpeg'};base64,${r.photo}`); })
      .catch((e) => { if (alive) setPhotoErr(e.message || 'Could not load photo'); });
    return () => { alive = false; };
  }, [order.id, order.photo.state]);

  const triggerDownload = (href, filename) => {
    const a = document.createElement('a');
    a.href = href; a.download = filename;
    document.body.appendChild(a); a.click(); a.remove();
  };

  const downloadPhoto = async (which) => {
    try {
      let href;
      if (which !== 'toy' && photoUrl) {
        href = photoUrl; // already loaded for the thumbnail
      } else {
        const r = await apiFetch(`/api/order?id=${encodeURIComponent(order.id)}&photo=${which === 'toy' ? 'toy' : '1'}`);
        href = `data:${r.mime || 'image/jpeg'};base64,${r.photo}`;
      }
      triggerDownload(href, `${order.slug}${which === 'toy' ? '-toy' : ''}.jpg`);
    } catch (e) { flash('danger', 'No photo', e.message); }
  };

  const startJob = async () => {
    setBusy('start');
    try {
      await apiFetch('/api/order', { method: 'POST', body: JSON.stringify({ id: order.id, action: 'start' }) });
      setStatus('art');
      onUpdate(order.id, { status: 'art' });
      flash('success', 'Started', `${order.petName} is now Art in progress.`);
    } catch (e) { flash('danger', 'Could not start', e.message); }
    setBusy(null);
  };

  const publish = async () => {
    if (!spriteData && !order.sprite.uploaded) { flash('warning', 'Add the sprite first', 'Upload the pixel sprite sheet to publish.'); return; }
    setBusy('publish');
    try {
      const body = { id: order.id, petType, spriteScale: Number(spriteScale) || 1 };
      if (spriteData) body.sprite = spriteData;        // omit -> server keeps the stored sprite
      if (slug !== order.slug) body.slug = slug;
      if (order.toy && toyData) body.toySprite = toyData;
      const r = await apiFetch('/api/publish', { method: 'POST', body: JSON.stringify(body) });
      setLink(r.url); setSlug(r.slug); setStatus('published');
      onUpdate(order.id, { status: 'published', slug: r.slug, game: { published: true, link: r.url }, sprite: { uploaded: true } });
      flash('success', 'Published', `${order.petName}'s game is live. Preview it, then deliver.`);
    } catch (e) { flash('danger', 'Could not publish', e.message); }
    setBusy(null);
  };

  const markReady = async () => {
    setBusy('ready');
    try {
      const r = await apiFetch('/api/mark-ready', { method: 'POST', body: JSON.stringify({ id: order.id }) });
      setStatus('ready');
      onUpdate(order.id, { status: 'ready' });
      flash(r.emailed ? 'success' : 'warning', r.emailed ? 'Sent!' : 'Delivered (email pending)', `${order.customer} can play ${order.petName} now.`);
    } catch (e) { flash('danger', 'Could not deliver', e.message); }
    setBusy(null);
  };

  const doRefund = async () => {
    setRefund(false);
    if (!can(role, 'confirmRefund')) { flash('info', 'Request sent', 'An admin will confirm the refund.'); return; }
    setBusy('refund');
    try {
      await apiFetch('/api/refund', { method: 'POST', body: JSON.stringify({ id: order.id }) });
      setStatus('refunded'); onUpdate(order.id, { status: 'refunded' });
      flash('success', 'Refunded', `${order.amount} returned to ${order.customer}.`);
    } catch (e) { flash('danger', 'Refund failed', e.message); }
    setBusy(null);
  };

  const moderateWall = async (action) => {
    if (!wall) return;
    setBusy('wall');
    try {
      const r = await apiFetch('/api/wall', { method: 'POST', body: JSON.stringify({ action, id: wall.id }) });
      setWall({ ...wall, status: r.status });
      flash('success', 'Wall updated', `This entry is now ${r.status}.`);
    } catch (e) { flash('danger', 'Wall action failed', e.message); }
    setBusy(null);
  };

  const spriteReady = !!spriteData || order.sprite.uploaded;
  const toyReady = !order.toy || !!toyData;

  return (
    <div>
      <button onClick={onBack} style={{ display: 'flex', alignItems: 'center', gap: 6, background: 'none', border: 'none', color: 'var(--plum-accent)', cursor: 'pointer', fontSize: 14, fontWeight: 700, marginBottom: 14, padding: 0 }}>
        <Icon name="arrow-left" /> All orders
      </button>

      <div style={{ display: 'grid', gridTemplateColumns: stack ? '1fr' : 'minmax(0,1.7fr) minmax(0,1fr)', gap: 18, alignItems: 'start' }} className="ao-grid">
        {/* MAIN COLUMN */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 16, minWidth: 0 }}>
          <Card surface="card" padding="lg">
            <div style={{ display: 'flex', gap: 16, alignItems: 'center', flexWrap: 'wrap' }}>
              <PetAvatar order={order} size="xl" photoSrc={photoUrl} />
              <div style={{ flex: 1, minWidth: 180 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
                  <h1 className="spk-h2" style={{ fontSize: 26, margin: 0 }}>{order.petName}</h1>
                  <StatusBadge status={status} />
                  {status !== 'ready' && status !== 'refunded' && <TimeLeftPill min={order.dueInMin} big />}
                </div>
                <p className="spk-muted" style={{ margin: '4px 0 0', fontSize: 14 }}>{order.id}{order.toy ? ' · with toy add-on' : ''}</p>
                <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 8, flexWrap: 'wrap', fontSize: 14, color: 'var(--plum-ink)' }}>
                  <span style={{ wordBreak: 'break-word', minWidth: 0 }}>{order.customer} · {order.email}</span>
                  <span style={{ fontWeight: 800 }}>{order.amount}</span>
                </div>
              </div>
            </div>
          </Card>

          {order.note && (
            <Card surface="card" padding="md">
              <p className="spk-eyebrow" style={{ marginBottom: 8 }}>CUSTOMER NOTE</p>
              <p style={{ margin: 0, fontSize: 15, color: 'var(--plum-ink)', lineHeight: 1.6 }}>“{order.note}”</p>
            </Card>
          )}

          {status === 'received' && (
            <Card surface="card" padding="md">
              <div style={{ display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' }}>
                <div style={{ flex: 1, minWidth: 180 }}>
                  <strong style={{ fontSize: 15, color: 'var(--plum-ink)' }}>Ready to start?</strong>
                  <p className="spk-muted" style={{ margin: '2px 0 0', fontSize: 13.5 }}>Marks this Art in progress so {order.customer.split(' ')[0]} sees a real person is on it.</p>
                </div>
                <Button variant="primary" disabled={busy === 'start'} onClick={startJob} leftIcon={<Icon name="play" />}>{busy === 'start' ? 'Starting…' : "I'm on it"}</Button>
              </div>
            </Card>
          )}

          <Section icon="edit" title="Pet name & game link">
            <p className="spk-muted" style={{ margin: '0 0 10px', fontSize: 14 }}>The game and its private link are named after the pet. Fix typos here before publishing.</p>
            <SlugEditor slug={slug} onChange={setSlug} />
          </Section>

          <Section icon="image" title="Customer photo" done={order.photo.state === 'deleted'}>
            {order.photo.state === 'present' ? (
              <div style={{ display: 'flex', gap: 14, alignItems: 'flex-start', flexWrap: 'wrap' }}>
                <div style={{ width: 104, height: 104, flex: '0 0 auto', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'var(--cream)', boxShadow: 'var(--ofx-edges)', overflow: 'hidden' }}>
                  {photoUrl
                    ? <img src={photoUrl} alt={`${order.petName} photo`} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
                    : <span className="spk-muted" style={{ fontSize: 12 }}>{photoErr ? <Icon name="image" /> : 'Loading…'}</span>}
                </div>
                <div style={{ flex: 1, minWidth: 180 }}>
                  <Button variant="secondary" leftIcon={<Icon name="download" />} onClick={() => downloadPhoto('pet')}>Download pet photo</Button>
                  {order.toy && order.hasToyPhoto && (
                    <Button variant="ghost" leftIcon={<Icon name="download" />} onClick={() => downloadPhoto('toy')} style={{ marginLeft: 8 }}>Download toy photo</Button>
                  )}
                  <p className="spk-muted" style={{ margin: '12px 0 0', fontSize: 13, display: 'flex', alignItems: 'center', gap: 6 }}>
                    <Icon name="clock" size={14} aria-hidden="true" /> Kept up to 48 hours after you deliver, then auto-deleted.
                  </p>
                </div>
              </div>
            ) : (
              <div style={{ display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap' }}>
                {order.sprite.uploaded && (
                  <img src={`/api/pet?slug=${encodeURIComponent(order.slug)}&asset=sprite`} alt={`${order.petName} sprite`} style={{ width: 104, height: 104, flex: '0 0 auto', objectFit: 'cover', imageRendering: 'pixelated', boxShadow: 'var(--ofx-edges)', background: 'var(--cream)' }} />
                )}
                <div style={{ display: 'flex', alignItems: 'center', gap: 10, color: 'var(--plum-accent)' }}>
                  <Icon name="trash" /> <span style={{ fontSize: 14, fontWeight: 700 }}>Photo deleted{order.photo.deletedDate ? ` ${order.photo.deletedDate}` : ''}.{order.sprite.uploaded ? ' Pixel sprite kept.' : ''}</span>
                </div>
              </div>
            )}
          </Section>

          <Section icon="image" title="Make the sprite" done={spriteReady}>
            <ol style={{ margin: '0 0 14px', paddingLeft: 20, color: 'var(--plum-ink)', fontSize: 14, lineHeight: 1.7 }}>
              {SPRITE_STEPS.map((s) => <li key={s}>{s}</li>)}
            </ol>
            <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 14 }}>
              <span className="spk-muted" style={{ fontSize: 13, fontWeight: 700 }}>This pet is a</span>
              {['cat', 'dog'].map((t) => (
                <button key={t} onClick={() => setPetType(t)} aria-pressed={petType === t} style={{ fontFamily: 'var(--font-body)', fontWeight: 800, fontSize: 12.5, cursor: 'pointer', padding: '5px 12px', border: 0, boxShadow: 'var(--ofx-edges)', background: petType === t ? 'var(--action)' : 'var(--cream-surface)', color: petType === t ? 'var(--cream)' : 'var(--plum-ink)' }}>{t === 'cat' ? 'Cat' : 'Dog'}</button>
              ))}
            </div>
            <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 14 }}>
              <span className="spk-muted" style={{ fontSize: 13, fontWeight: 700 }}>In-game size</span>
              <input type="number" min="0.5" max="3" step="0.25" value={spriteScale}
                onChange={(e) => setSpriteScale(e.target.value)}
                style={{ width: 64, fontFamily: 'var(--font-body)', fontWeight: 800, fontSize: 12.5, padding: '5px 8px', border: 0, boxShadow: 'var(--ofx-edges)', background: 'var(--cream-surface)', color: 'var(--plum-ink)' }} />
              <span className="spk-muted" style={{ fontSize: 12.5 }}>×&nbsp; (1 = normal, 1.5 = 50% bigger)</span>
            </div>
            <SpriteUpload label="Upload sprite sheet" hint="The 3×3 sheet you exported (idle, walk, jump)." value={spriteData} onPick={(d) => setSpriteData(d)} />
            <div style={{ marginTop: 12 }}>
              <Button variant="ghost" size="sm" leftIcon={<Icon name="grid" />} onClick={() => {
                const p = new URLSearchParams({ pet: order.petName, order: order.id });
                // Only point the mapper at a real sheet once one is published; otherwise it falls back to upload/demo.
                if (spriteReady && order.slug) p.set('sheet', `/assets/pets/${order.slug}/sprite_3x3.png`);
                window.open(`sprite-mapper.html?${p.toString()}`, '_blank', 'noopener');
              }}>Sprite mapper — map frames &amp; actions</Button>
              <p className="spk-muted" style={{ margin: '6px 0 0', fontSize: 12.5 }}>Open the mapper to crop the 9 frames and chain them into idle / run / jump animations.</p>
            </div>
            {order.toy && (
              <div style={{ marginTop: 16, paddingTop: 16, borderTop: '2px solid var(--border-soft)' }}>
                <p className="spk-eyebrow" style={{ marginBottom: 8 }}>FAVORITE TOY ADD-ON ($5)</p>
                <p className="spk-muted" style={{ margin: '0 0 10px', fontSize: 13.5 }}>Pixel-ify the toy from its photo and upload it too.</p>
                <SpriteUpload label="Upload toy sprite" hint="A small pixel image of the toy." value={toyData} onPick={(d) => setToyData(d)} />
              </div>
            )}
          </Section>

          <Section icon="gamepad" title="Generate & publish" done={published}>
            {!published ? (
              <div>
                <p className="spk-muted" style={{ margin: '0 0 12px', fontSize: 14 }}>Publishes {order.petName}’s game to a private link. The customer is not emailed yet, so you can preview first.</p>
                <Button variant="primary" leftIcon={<Icon name="play" />} disabled={!spriteReady || !toyReady || busy === 'publish'} onClick={publish}>
                  {busy === 'publish' ? 'Publishing…' : `Generate & publish ${order.petName}’s game`}
                </Button>
                {!spriteReady && <p className="spk-muted" style={{ margin: '8px 0 0', fontSize: 12.5 }}>Upload the sprite sheet first.</p>}
                {spriteReady && !toyReady && <p className="spk-muted" style={{ margin: '8px 0 0', fontSize: 12.5 }}>Upload the toy sprite too (this order has the toy add-on).</p>}
              </div>
            ) : (
              <div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '10px 12px', background: 'var(--cream)', boxShadow: 'var(--ofx-edges)' }}>
                  <Icon name="link" size={16} aria-hidden="true" />
                  <a href={link} target="_blank" rel="noreferrer" style={{ fontFamily: 'var(--font-pixel)', fontSize: 10, color: 'var(--plum-ink)', flex: 1, wordBreak: 'break-all' }}>{link}</a>
                  <Badge tone="neutral" dot>Live</Badge>
                </div>
                <Button variant="ghost" size="sm" leftIcon={<Icon name="reload" />} disabled={busy === 'publish'} onClick={publish} style={{ marginTop: 10 }}>
                  {busy === 'publish' ? 'Re-publishing…' : 'Re-publish (after fixing the sprite)'}
                </Button>
              </div>
            )}
          </Section>

          {published && (
            <Section icon="play" title="Preview the game">
              <p className="spk-muted" style={{ margin: '0 0 12px', fontSize: 14 }}>Check it looks right before you deliver.</p>
              <iframe title="game preview" src={link} style={{ width: '100%', aspectRatio: '16 / 9', border: 0, boxShadow: 'var(--ofx-edges)', background: 'var(--sky)' }} />
              <a href={link} target="_blank" rel="noreferrer" style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontWeight: 700, fontSize: 13, marginTop: 8 }}>Open in a new tab <Icon name="external-link" size={13} /></a>
            </Section>
          )}

          <Section icon="mail" title="Deliver" done={status === 'ready'}>
            {status === 'ready' ? (
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <Badge tone="success" dot>Delivered</Badge>
                <span className="spk-muted" style={{ fontSize: 14 }}>Play link emailed to {order.email}.</span>
              </div>
            ) : (
              <div>
                <p className="spk-muted" style={{ margin: '0 0 12px', fontSize: 14 }}>Emails {order.customer} the private play link.</p>
                <Button variant="primary" leftIcon={<Icon name="check" />} disabled={!published || busy === 'ready'} onClick={markReady}>
                  {busy === 'ready' ? 'Sending…' : `Mark ready & email ${order.customer.split(' ')[0]}`}
                </Button>
                {!published && <p className="spk-muted" style={{ margin: '8px 0 0', fontSize: 12.5 }}>Publish the game first.</p>}
              </div>
            )}
          </Section>

          {wall && (
            <Section icon="heart" title="Pet wall">
              <div style={{ display: 'flex', gap: 12, alignItems: 'center', marginBottom: 12, flexWrap: 'wrap' }}>
                <Badge tone={wall.status === 'approved' ? 'success' : wall.status === 'removed' ? 'danger' : 'neutral'} dot>
                  {wall.status === 'approved' ? 'Live on the wall' : wall.status === 'hidden' ? 'Hidden' : wall.status === 'removed' ? 'Removed' : 'Pending review'}
                </Badge>
                <span className="spk-muted" style={{ fontSize: 13 }}>Customer opted in {fmtAgo(wall.consent_at)}.</span>
              </div>
              {wall.status !== 'removed' && (
                <div style={{ display: 'flex', gap: 12, marginBottom: 12, flexWrap: 'wrap' }}>
                  {wall.has_sprite && <img src={`/api/pet?slug=${encodeURIComponent(order.slug)}&asset=sprite`} alt="sprite" style={{ width: 84, height: 84, objectFit: 'cover', imageRendering: 'pixelated', boxShadow: 'var(--ofx-edges)', background: 'var(--cream)' }} />}
                  {wall.has_photo && <img src={`/api/wall?id=${encodeURIComponent(wall.id)}&asset=photo`} alt="photo" style={{ width: 84, height: 84, objectFit: 'cover', boxShadow: 'var(--ofx-edges)', background: 'var(--cream)' }} />}
                </div>
              )}
              <p className="spk-muted" style={{ margin: '0 0 12px', fontSize: 13.5 }}>Nothing is public until you approve. Approve shows {order.petName} on the public wall; remove is a takedown that deletes the copied images.</p>
              <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                {wall.status !== 'approved' && wall.status !== 'removed' && <Button variant="primary" size="sm" disabled={busy === 'wall'} onClick={() => moderateWall('approve')} leftIcon={<Icon name="check" />}>Approve for the wall</Button>}
                {wall.status === 'approved' && <Button variant="ghost" size="sm" disabled={busy === 'wall'} onClick={() => moderateWall('hide')}>Hide from the wall</Button>}
                {wall.status !== 'removed' && <Button variant="danger" size="sm" disabled={busy === 'wall'} onClick={() => moderateWall('remove')}>Remove (takedown)</Button>}
              </div>
            </Section>
          )}

          {status !== 'refunded' && (
            <div style={{ boxShadow: 'var(--ofx-edges)', padding: 16, background: 'var(--danger-tint)', ['--ofx-color']: 'var(--danger)' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
                <Icon name="alert" size={16} aria-hidden="true" />
                <strong style={{ fontSize: 15, color: 'var(--plum-ink)' }}>Refund</strong>
              </div>
              <p className="spk-muted" style={{ margin: '0 0 12px', fontSize: 13.5 }}>
                Refunds the {order.amount} via Stripe. {can(role, 'confirmRefund') ? 'Confirmed by you (admin).' : 'You can request this; an admin confirms.'}
              </p>
              <Button variant="danger" disabled={busy === 'refund'} onClick={() => setRefund(true)}>{can(role, 'confirmRefund') ? `Refund ${order.amount}` : 'Request a refund'}</Button>
            </div>
          )}
        </div>

        {/* SIDE COLUMN */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 16, minWidth: 0 }}>
          <Card surface="cream" padding="md">
            <p className="spk-eyebrow" style={{ marginBottom: 10 }}>CUSTOMER</p>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8 }}>
              <Avatar name={order.customer} size="sm" />
              <div><strong style={{ color: 'var(--plum-ink)', fontSize: 15 }}>{order.customer}</strong><div className="spk-muted" style={{ fontSize: 13, wordBreak: 'break-all' }}>{order.email}</div></div>
            </div>
            <div className="spk-muted" style={{ fontSize: 13 }}>Paid {order.paidAt} · {order.amount}</div>
          </Card>

          <Card surface="card" padding="md">
            <p className="spk-eyebrow" style={{ marginBottom: 10 }}>ORDER</p>
            <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 14, color: 'var(--plum-ink)', marginBottom: 6 }}><span>{order.petName}’s platformer</span><span>$29</span></div>
            {order.toy && <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 14, color: 'var(--plum-ink)', marginBottom: 6 }}><span>Favorite toy add-on</span><span>$5</span></div>}
            <div style={{ display: 'flex', justifyContent: 'space-between', fontWeight: 800, color: 'var(--plum-ink)', borderTop: '2px solid var(--border-soft)', paddingTop: 8, marginTop: 4 }}><span>Total</span><span>{order.amount}</span></div>
          </Card>

          <Card surface="card" padding="md">
            <p className="spk-eyebrow" style={{ marginBottom: 12 }}>ACTIVITY</p>
            <ActivityLog events={events} />
          </Card>
        </div>
      </div>

      <Dialog open={refund} onClose={() => setRefund(false)} title={can(role, 'confirmRefund') ? `Refund ${order.customer}?` : `Request a refund for ${order.customer}?`}
        footer={<React.Fragment>
          <Button variant="ghost" onClick={() => setRefund(false)}>Cancel</Button>
          {can(role, 'confirmRefund')
            ? <Button variant="danger" onClick={doRefund}>Refund {order.amount}</Button>
            : <Button variant="primary" onClick={doRefund}>Send request to admin</Button>}
        </React.Fragment>}>
        <p style={{ margin: 0, fontSize: 14.5, color: 'var(--plum-ink)', lineHeight: 1.5 }}>
          {can(role, 'confirmRefund')
            ? <span>This refunds {order.customer} {order.amount} in full via Stripe. This can’t be undone.</span>
            : <span>An admin confirms refunds. They’ll get a note to approve {order.amount} back to {order.customer}.</span>}
        </p>
      </Dialog>

      {toast && (
        <div style={{ position: 'fixed', left: '50%', bottom: 18, transform: 'translateX(-50%)', zIndex: 1000, width: 'min(92%, 420px)' }}>
          <Toast tone={toast.tone} title={toast.title} onDismiss={() => setToast(null)}>{toast.msg}</Toast>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { OrderDetail });
