// route-editor.jsx — THE app editor. Point-click + chat + versions + live preview.
// Inspired by Lovable (chat+preview split), Vercel v0 (versions), Shopify Sidekick (Polaris feel).

const PICKABLE_ELEMENTS = [
  // {id, label, tagPath, defaultText, type}
  { id: 'page-title', label: 'Page title', tagPath: 'Page.title', text: 'Active Coupons', type: 'text' },
  { id: 'page-meta', label: 'Page metadata', tagPath: 'Page.titleMetadata', text: '6 codes', type: 'text' },
  { id: 'stat-active', label: 'Active stat', tagPath: 'Card[stat=active]', text: '4 · live', type: 'metric' },
  { id: 'stat-redeem', label: 'Redemptions stat', tagPath: 'Card[stat=redemptions]', text: '128', type: 'metric' },
  { id: 'row-summer25', label: 'Coupon row (SUMMER25)', tagPath: 'IndexTable.Row[code=SUMMER25]', text: 'SUMMER25', type: 'row' },
  { id: 'badge-active', label: 'Status badge', tagPath: 'Badge tone="success"', text: 'Active', type: 'badge' },
  { id: 'btn-new', label: 'New coupon button', tagPath: 'Button variant="primary"', text: 'New coupon', type: 'button' },
];

function RouteEditor({ navigate, state, setState }) {
  const e = state.editor;
  const set = (patch) => setState((s) => ({ ...s, editor: { ...s.editor, ...(typeof patch === 'function' ? patch(s.editor) : patch) } }));
  const [draft, setDraft] = React.useState('');
  const [hoveredEl, setHoveredEl] = React.useState(null);
  const [aiBusy, setAiBusy] = React.useState(false);
  const [pendingChange, setPendingChange] = React.useState(null); // shows "changes preview" callout
  const chatScrollRef = React.useRef(null);

  // Surface: which view of the app to preview (admin operator vs storefront customer)
  const appMeta = (window.getPreview ? window.getPreview(e.appName) : { surfaces: ['admin'], home: 'admin' });
  const [surface, setSurface] = React.useState(appMeta.home || 'admin');
  // Keep surface valid if the editor's app changes (e.g. after fresh scaffold)
  React.useEffect(() => {
    if (!appMeta.surfaces.includes(surface)) setSurface(appMeta.home || appMeta.surfaces[0] || 'admin');
  }, [e.appName]); // eslint-disable-line

  React.useEffect(() => {
    if (chatScrollRef.current) chatScrollRef.current.scrollTop = chatScrollRef.current.scrollHeight;
  }, [e.chat, aiBusy]);

  const selectEl = (el) => {
    set({ selectedEl: el });
  };

  const togglePointMode = () => set({ pointMode: !e.pointMode });

  const sendChat = (text, attachedEl) => {
    if (!text.trim() && !attachedEl) return;
    const newMsg = { role: 'user', content: text, attachedEl, ts: 'just now' };
    set((ee) => ({ chat: [...ee.chat, newMsg], selectedEl: null }));
    setDraft('');
    setAiBusy(true);

    // canned AI response based on what was selected
    setTimeout(() => {
      const versionId = 'v' + (e.versions.length + 1);
      let response;
      if (attachedEl && attachedEl.id === 'page-title') {
        response = {
          role: 'ai', ts: 'just now',
          content: `Updated **\`Page.title\`** — smaller font + appended live count.`,
          change: {
            file: 'apps/coupon-manager/page.tsx',
            diffLines: 4,
            version: versionId,
            summary: 'Shrink page title 24 → 18px · append titleMetadata "· 24 active"',
          },
        };
      } else if (attachedEl && attachedEl.id === 'badge-active') {
        response = {
          role: 'ai', ts: 'just now',
          content: `Changed Badge tone from \`success\` to \`magic\` with a dot indicator.`,
          change: { file: 'apps/coupon-manager/list.tsx', diffLines: 2, version: versionId, summary: 'Badge tone success → magic · added dot prop' },
        };
      } else if (attachedEl && attachedEl.id === 'btn-new') {
        response = {
          role: 'ai', ts: 'just now',
          content: `Added confirmation modal before creating a new coupon.`,
          change: { file: 'apps/coupon-manager/list.tsx', diffLines: 18, version: versionId, summary: 'Wrap "New coupon" action in a Polaris Modal with form fields' },
        };
      } else {
        response = {
          role: 'ai', ts: 'just now',
          content: `Done — applied your change.`,
          change: { file: 'apps/coupon-manager/page.tsx', diffLines: 3, version: versionId, summary: 'Inline tweak from chat instruction' },
        };
      }
      setAiBusy(false);
      set((ee) => ({
        chat: [...ee.chat, response],
        versions: [{ id: versionId, label: response.change.summary, ts: 'just now', live: false, draft: true }, ...ee.versions.map((v) => ({ ...v, draft: false }))],
      }));
      setPendingChange({ version: versionId, summary: response.change.summary });
    }, 1400);
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      {/* App context bar */}
      <div style={{
        padding: '10px 18px', background: 'var(--p-color-bg-surface)',
        borderBottom: '1px solid var(--p-color-border)',
        display: 'flex', alignItems: 'center', gap: 12, flexShrink: 0,
      }}>
        <Btn variant="plain" size="sm" icon={I.chevronLeft} onClick={() => navigate('creator')}>My apps</Btn>
        <div style={{ width: 1, height: 18, background: 'var(--p-color-border)' }} />
        <div style={{
          width: 26, height: 26, borderRadius: 7,
          background: 'var(--p-color-bg-fill-magic)',
          color: 'var(--p-color-text-magic)',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          fontWeight: 600, fontSize: 12,
        }}>{e.appName[0]}</div>
        <div style={{ fontSize: 14, fontWeight: 600 }}>{e.appName}</div>
        {e.versions[0]?.draft ? (
          <Badge tone="magic" dot>{e.versions[0].id} · Draft</Badge>
        ) : (
          <Badge tone="success" dot>{e.versions[0]?.id} · Live</Badge>
        )}
        {e.versions[0]?.draft && (
          <span style={{ fontSize: 12, color: 'var(--p-color-text-secondary)' }}>
            · editing on top of {e.versions.find((v) => v.live)?.id || 'v0'}
          </span>
        )}
        <div style={{ flex: 1 }} />
        <Btn variant={e.pointMode ? 'primary' : 'secondary'} size="sm" icon={I.cursor} pressed={e.pointMode}
          onClick={togglePointMode}>Point &amp; click</Btn>
        <Btn size="sm" icon={I.external}>Preview</Btn>
        <Btn variant="plain" size="sm">Discard</Btn>
        {e.versions[0]?.draft && (
          <Btn variant="primary" size="sm" onClick={() => {
            set((ee) => ({
              versions: ee.versions.map((v, i) => i === 0 ? { ...v, draft: false, live: true } : { ...v, live: false }),
              liveVersion: ee.versions[0].id,
              chat: [...ee.chat, {
                role: 'system', ts: 'just now',
                content: `Applied **${ee.versions[0].id}** to live. Operations users now see this version.`,
              }],
            }));
            setPendingChange(null);
          }}>Apply {e.versions[0]?.id}</Btn>
        )}
      </div>

      <div style={{ flex: 1, minHeight: 0, display: 'flex' }}>
        {/* LEFT — preview */}
        <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', background: 'var(--p-color-bg)' }}>
          {/* Preview toolbar — surface switcher + state */}
          <div style={{
            padding: '8px 14px', borderBottom: '1px solid var(--p-color-border-secondary)',
            background: 'var(--p-color-bg-surface)',
            display: 'flex', alignItems: 'center', gap: 10, fontSize: 12,
            color: 'var(--p-color-text-secondary)',
          }}>
            <SurfaceTabs surfaces={appMeta.surfaces} value={surface} onChange={setSurface} />
            <div style={{ width: 1, height: 18, background: 'var(--p-color-border)' }} />
            {e.pointMode && surface === 'admin' ? (
              <>
                <I.cursor size={12} />
                <span><b style={{ color: 'var(--p-color-text-magic)' }}>Point mode</b> · click any element to attach to chat</span>
              </>
            ) : surface === 'storefront' ? (
              <>
                <I.store size={12} />
                <span>Storefront context · sarahslinen.com</span>
                <span style={{ color: 'var(--p-color-text-disabled)' }}>· fake data</span>
              </>
            ) : (
              <>
                <I.apps size={12} />
                <span>Admin context · Operations</span>
                <span style={{ color: 'var(--p-color-text-disabled)' }}>· {e.versions[0]?.id} {e.versions[0]?.draft && '(draft)'}</span>
              </>
            )}
            <div style={{ flex: 1 }} />
            <Chip>Desktop</Chip>
            <Chip>Mobile</Chip>
            <span className="p-mono" style={{ color: 'var(--p-color-text-disabled)' }}>1280 × 800</span>
          </div>

          {/* The actual app preview — switches by surface */}
          <div className="p-scroll" style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: surface === 'storefront' ? 16 : 24, background: surface === 'storefront' ? 'var(--p-color-bg-fill-tertiary)' : 'var(--p-color-bg)' }}>
            {surface === 'admin' && (
              <CouponManagerPreview
                pointMode={e.pointMode}
                selectedEl={e.selectedEl}
                hoveredEl={hoveredEl}
                setHoveredEl={setHoveredEl}
                onPick={selectEl}
                versionId={e.versions[0]?.id}
              />
            )}
            {surface === 'storefront' && (
              <EditorStorefrontPreview appName={e.appName} appMeta={appMeta} />
            )}
          </div>

          {/* Floating "changes ready" toast (Lovable / v0 style) */}
          {pendingChange && (
            <div style={{
              position: 'absolute', bottom: 24, left: '25%', transform: 'translateX(-50%)',
              background: 'var(--p-color-bg-inverse)', color: 'white',
              padding: '10px 14px', borderRadius: 12,
              display: 'flex', alignItems: 'center', gap: 10,
              boxShadow: '0 8px 24px rgba(0,0,0,0.18)',
              fontSize: 13, zIndex: 20,
            }} className="p-fade-in">
              <I.sparkle size={14} />
              <span><b>{pendingChange.version}</b> applied to preview · {pendingChange.summary}</span>
              <Btn size="sm" style={{ marginLeft: 6 }} onClick={() => setPendingChange(null)}>Dismiss</Btn>
              <Btn variant="primary" size="sm" onClick={() => {
                set((ee) => ({
                  versions: ee.versions.map((v, i) => i === 0 ? { ...v, draft: false, live: true } : { ...v, live: false }),
                  liveVersion: ee.versions[0].id,
                }));
                setPendingChange(null);
              }}>Apply to live</Btn>
            </div>
          )}
        </div>

        {/* RIGHT — chat / versions sidebar */}
        <div style={{
          width: 400, borderLeft: '1px solid var(--p-color-border)',
          background: 'var(--p-color-bg-surface)', display: 'flex', flexDirection: 'column',
          flexShrink: 0,
        }}>
          <Tabs
            tabs={[
              { id: 'chat', label: 'Chat' },
              { id: 'versions', label: 'Versions', count: e.versions.length },
              { id: 'settings', label: 'Settings' },
            ]}
            value={e.tab}
            onChange={(t) => set({ tab: t })}
          />

          {e.tab === 'chat' && (
            <ChatPane
              chat={e.chat}
              aiBusy={aiBusy}
              draft={draft}
              setDraft={setDraft}
              selectedEl={e.selectedEl}
              clearSelection={() => set({ selectedEl: null })}
              onSend={sendChat}
              scrollRef={chatScrollRef}
              pointModeOn={e.pointMode}
              togglePointMode={togglePointMode}
            />
          )}

          {e.tab === 'versions' && (
            <VersionsPane
              versions={e.versions}
              onRollback={(v) => set({ showRollback: v })}
              onSelectVersion={(v) => alert('Preview ' + v + ' — wire up in real impl')}
            />
          )}

          {e.tab === 'settings' && (
            <SettingsPane />
          )}
        </div>
      </div>

      {/* Rollback dialog */}
      {e.showRollback && (
        <Dialog
          title={`Rollback to ${e.showRollback}?`}
          icon={I.rollback}
          onClose={() => set({ showRollback: null })}
          footer={
            <>
              <Btn onClick={() => set({ showRollback: null })}>Cancel</Btn>
              <Btn variant="primary" icon={I.rollback} onClick={() => {
                set((ee) => ({
                  versions: ee.versions.map((v) => ({ ...v, live: v.id === ee.showRollback, draft: false })),
                  liveVersion: ee.showRollback,
                  showRollback: null,
                  chat: [...ee.chat, { role: 'system', ts: 'just now', content: `Rolled back to **${ee.showRollback}**. Live for all Operations users.` }],
                }));
              }}>Rollback &amp; apply</Btn>
            </>
          }
        >
          This will swap your live UI back to <b>{e.showRollback}</b>. The current version stays in history — you can re-apply anytime.
          <div style={{
            marginTop: 12, background: 'var(--p-color-bg-surface-secondary)',
            border: '1px solid var(--p-color-border)', borderRadius: 8,
            padding: 10, fontSize: 12, color: 'var(--p-color-text-secondary)',
          }}>
            <b style={{ color: 'var(--p-color-text)' }}>What changes:</b>
            <div>− Reverts visual + logic to {e.showRollback}'s state</div>
            <div>− No data lost · coupon records stay intact</div>
            <div>− Storefront widgets unaffected</div>
          </div>
        </Dialog>
      )}
    </div>
  );
}

// ─── Coupon Manager — the preview app being edited ──────────
function CouponManagerPreview({ pointMode, selectedEl, hoveredEl, setHoveredEl, onPick, versionId }) {
  // Wrap each pickable element with hover/click handlers when pointMode is on.
  const pickable = (id, children, style) => {
    const el = PICKABLE_ELEMENTS.find((x) => x.id === id);
    const isHover = pointMode && hoveredEl === id;
    const isSel = selectedEl?.id === id;
    return (
      <span
        onMouseEnter={() => pointMode && setHoveredEl(id)}
        onMouseLeave={() => pointMode && setHoveredEl(null)}
        onClick={(ev) => { if (pointMode) { ev.stopPropagation(); onPick(el); } }}
        style={{
          cursor: pointMode ? 'crosshair' : 'inherit',
          display: 'inline-block',
          position: 'relative',
          ...(isHover ? { outline: '1.5px dashed rgba(107,70,193,0.6)', outlineOffset: 2, borderRadius: 4 } : {}),
          ...(isSel ? { outline: '2px solid var(--p-color-bg-fill-magic-active)', outlineOffset: 3, borderRadius: 4 } : {}),
          ...(style || {}),
        }}
      >
        {(isSel || isHover) && (
          <div style={{
            position: 'absolute', top: -22, left: -2,
            background: isSel ? 'var(--p-color-bg-fill-magic-active)' : 'rgba(107,70,193,0.6)',
            color: 'white', padding: '2px 6px', borderRadius: 4,
            fontFamily: 'var(--p-font-family-mono)', fontSize: 10.5,
            whiteSpace: 'nowrap', zIndex: 5,
          }}>{isSel ? 'selected · ' : ''}{el?.tagPath}</div>
        )}
        {children}
      </span>
    );
  };

  return (
    <div style={{ maxWidth: 1000, margin: '0 auto' }}>
      <div className="p-page-head">
        <div>
          <div className="p-eyebrow">Operations · Coupon Manager</div>
          {pickable('page-title',
            <span className="p-page-title">Active Coupons</span>
          )}
          {' '}
          {pickable('page-meta',
            <span style={{ color: 'var(--p-color-text-secondary)', fontSize: 16, fontWeight: 500 }}>· 6 codes</span>
          )}
        </div>
        <div style={{ display: 'flex', gap: 8 }}>
          <Btn iconRight={I.chevronDown}>Bulk actions</Btn>
          {pickable('btn-new',
            <Btn variant="primary" icon={I.plus}>New coupon</Btn>
          )}
        </div>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', gap: 12, marginBottom: 16 }}>
        {pickable('stat-active',
          <Card padding style={{ minWidth: 0 }}>
            <div style={{ fontSize: 11, fontWeight: 500, color: 'var(--p-color-text-secondary)', textTransform: 'uppercase', letterSpacing: 0.04 }}>Active</div>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginTop: 4 }}>
              <div style={{ fontSize: 22, fontWeight: 700 }}>4</div>
              <Badge tone="success" dot>live</Badge>
            </div>
          </Card>
        )}
        {pickable('stat-redeem',
          <Card padding style={{ minWidth: 0 }}>
            <div style={{ fontSize: 11, fontWeight: 500, color: 'var(--p-color-text-secondary)', textTransform: 'uppercase', letterSpacing: 0.04 }}>Redemptions today</div>
            <div style={{ fontSize: 22, fontWeight: 700, marginTop: 4 }}>128</div>
          </Card>
        )}
        <Card padding>
          <div style={{ fontSize: 11, fontWeight: 500, color: 'var(--p-color-text-secondary)', textTransform: 'uppercase', letterSpacing: 0.04 }}>Revenue (mo)</div>
          <div style={{ fontSize: 22, fontWeight: 700, marginTop: 4 }}>$3,420</div>
        </Card>
        <Card padding>
          <div style={{ fontSize: 11, fontWeight: 500, color: 'var(--p-color-text-secondary)', textTransform: 'uppercase', letterSpacing: 0.04 }}>Avg. discount</div>
          <div style={{ fontSize: 22, fontWeight: 700, marginTop: 4 }}>18%</div>
        </Card>
      </div>

      <Card>
        <div className="p-table__head" style={{ gridTemplateColumns: '28px 2fr 2fr 1fr 1fr 1.5fr 32px' }}>
          <Checkbox />
          <div>Code</div><div>Discount</div><div>Status</div><div>Used</div><div>Validity</div><div></div>
        </div>
        {[
          ['SUMMER25', '25% off', 'Active', 'success', '1,042', 'Jun 1 – Aug 31', 'row-summer25'],
          ['WELCOME10', '$10 off first', 'Active', 'success', '612', 'No end', null],
          ['SHIPFREE', 'Free shipping', 'Active', 'success', '2,380', 'Ongoing', null],
          ['FLASH50', '50% off · flash', 'Scheduled', 'info', '0', 'Jun 30 · 8–9pm', null],
        ].map((r, i) => {
          const rowContent = (
            <div className="p-table__row" style={{ gridTemplateColumns: '28px 2fr 2fr 1fr 1fr 1.5fr 32px' }}>
              <Checkbox />
              <div className="p-mono" style={{ fontWeight: 600 }}>{r[0]}</div>
              <div>{r[1]}</div>
              <div>{i === 0 ? pickable('badge-active', <Badge tone={r[3]}>{r[2]}</Badge>) : <Badge tone={r[3]}>{r[2]}</Badge>}</div>
              <div className="p-mono" style={{ color: 'var(--p-color-text-secondary)' }}>{r[4]}</div>
              <div style={{ color: 'var(--p-color-text-secondary)', fontSize: 12 }}>{r[5]}</div>
              <Btn variant="plain" size="sm" icon={I.more} />
            </div>
          );
          if (r[6]) return <div key={i}>{pickable(r[6], rowContent, { display: 'block' })}</div>;
          return <React.Fragment key={i}>{rowContent}</React.Fragment>;
        })}
      </Card>
    </div>
  );
}

// ─── Chat pane ─────────────────────────────────────────────
function ChatPane({ chat, aiBusy, draft, setDraft, selectedEl, clearSelection, onSend, scrollRef, pointModeOn, togglePointMode }) {
  return (
    <>
      <div ref={scrollRef} className="p-scroll" style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: '14px 14px 12px' }}>
        {chat.map((m, i) => <EditorChatMsg key={i} m={m} />)}
        {aiBusy && (
          <div style={{ display: 'flex', gap: 10, alignItems: 'flex-start', marginBottom: 12 }}>
            <AIAvatar size={24} />
            <div style={{
              padding: '8px 12px', background: 'var(--p-color-bg-fill-magic)',
              borderRadius: '4px 12px 12px 12px', color: 'var(--p-color-text-magic)',
              fontSize: 12.5, display: 'flex', alignItems: 'center', gap: 6,
            }}>
              <ThinkingDots /> applying your change…
            </div>
          </div>
        )}
      </div>

      <div style={{ padding: 10, borderTop: '1px solid var(--p-color-border-secondary)' }}>
        <Card style={{ padding: 8 }}>
          {selectedEl && (
            <div style={{
              display: 'inline-flex', alignItems: 'center', gap: 6,
              background: 'var(--p-color-bg-fill-magic)',
              color: 'var(--p-color-text-magic)',
              padding: '3px 6px 3px 8px', borderRadius: 6,
              fontSize: 11.5, marginBottom: 7,
            }}>
              <I.cursor size={11} />
              <span className="p-mono">{selectedEl.tagPath}</span>
              <Btn variant="plain" size="sm" icon={I.close} onClick={clearSelection} style={{ padding: 2, minHeight: 18 }} />
            </div>
          )}
          <Input
            multiline rows={2}
            value={draft}
            onChange={setDraft}
            onKeyDown={(e) => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); onSend(draft, selectedEl); } }}
            placeholder={selectedEl ? `Tell AI what to change about ${selectedEl.label}…` : 'Describe a change · or pick an element first'}
          />
          <div style={{ display: 'flex', alignItems: 'center', gap: 4, marginTop: 6 }}>
            <Btn variant={pointModeOn ? 'primary' : 'plain'} size="sm" icon={I.cursor}
              pressed={pointModeOn} onClick={togglePointMode} title="Toggle point mode" />
            <Btn variant="plain" size="sm" icon={I.paperclip} title="Attach screenshot" />
            <div style={{ flex: 1 }} />
            <span style={{ fontSize: 11, color: 'var(--p-color-text-secondary)' }}><span className="p-mono">⌘↵</span></span>
            <Btn variant="magic" size="sm" icon={I.send} onClick={() => onSend(draft, selectedEl)}
              disabled={(!draft.trim() && !selectedEl) || aiBusy}>Send</Btn>
          </div>
        </Card>
      </div>
    </>
  );
}

function EditorChatMsg({ m }) {
  if (m.role === 'system') {
    return (
      <div style={{
        fontSize: 11.5, color: 'var(--p-color-text-secondary)',
        textAlign: 'center', padding: '8px 0', marginBottom: 8,
        background: 'var(--p-color-bg-fill-tertiary)', borderRadius: 6,
      }} dangerouslySetInnerHTML={{ __html: renderMd(m.content) }} />
    );
  }
  if (m.role === 'user') {
    return (
      <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', marginBottom: 12 }}>
        {m.attachedEl && (
          <div style={{
            display: 'inline-flex', alignItems: 'center', gap: 5,
            background: 'var(--p-color-bg-fill-tertiary)',
            padding: '3px 7px', borderRadius: 6, fontSize: 11,
            marginBottom: 4, color: 'var(--p-color-text-secondary)',
          }}>
            <I.cursor size={10} />
            <span className="p-mono">{m.attachedEl.tagPath}</span>
          </div>
        )}
        <div style={{
          maxWidth: 290, background: 'var(--p-color-bg-inverse)', color: 'white',
          padding: '8px 11px', borderRadius: '12px 12px 4px 12px',
          fontSize: 12.5, lineHeight: 1.45,
        }}>{m.content || <i style={{ opacity: 0.7 }}>(no message — just selected element)</i>}</div>
        <div style={{ fontSize: 10.5, color: 'var(--p-color-text-secondary)', marginTop: 3 }}>{m.ts}</div>
      </div>
    );
  }
  // AI
  return (
    <div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
      <AIAvatar size={24} />
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{
          background: 'var(--p-color-bg-surface-secondary)',
          padding: '8px 11px', borderRadius: '4px 12px 12px 12px',
          fontSize: 12.5, lineHeight: 1.5,
        }} dangerouslySetInnerHTML={{ __html: renderMd(m.content) }} />
        {m.change && (
          <div style={{
            marginTop: 6, padding: '8px 10px',
            background: 'var(--p-color-bg-fill-magic)',
            color: 'var(--p-color-text-magic)',
            border: '1px solid rgba(107,70,193,0.2)',
            borderRadius: 8, fontSize: 11.5,
          }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
              <I.layers size={11} />
              <b>{m.change.version}</b> · {m.change.diffLines} lines
              <div style={{ flex: 1 }} />
              <Btn variant="plain" size="sm" icon={I.diff} style={{ padding: '2px 6px', minHeight: 18, color: 'inherit' }}>Diff</Btn>
            </div>
            <div className="p-mono" style={{ fontSize: 10.5, opacity: 0.85 }}>{m.change.file}</div>
          </div>
        )}
        <div style={{ fontSize: 10.5, color: 'var(--p-color-text-secondary)', marginTop: 3 }}>{m.ts}</div>
      </div>
    </div>
  );
}

function VersionsPane({ versions, onRollback }) {
  return (
    <div className="p-scroll" style={{ flex: 1, overflow: 'auto', padding: 12 }}>
      <div style={{ fontSize: 11, color: 'var(--p-color-text-secondary)', textTransform: 'uppercase', letterSpacing: 0.06, fontWeight: 500, marginBottom: 8 }}>
        Linear history · {versions.length} versions
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
        {versions.map((v, i) => (
          <Card key={v.id} style={{
            padding: 10,
            borderColor: v.live ? 'var(--p-color-border-success)' : v.draft ? 'var(--p-color-border-magic)' : 'var(--p-color-border)',
            boxShadow: v.live ? '0 0 0 1px var(--p-color-border-success)' : v.draft ? '0 0 0 1px var(--p-color-border-magic)' : 'var(--p-shadow-100)',
          }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
              <span className="p-mono" style={{ fontWeight: 700, fontSize: 12 }}>{v.id}</span>
              {v.live && <Badge tone="success" dot>Live</Badge>}
              {v.draft && <Badge tone="magic" dot>Draft</Badge>}
              <div style={{ flex: 1 }} />
              <Btn variant="plain" size="sm" icon={I.diff} title="Diff" />
              {!v.live && !v.draft && <Btn size="sm" onClick={() => onRollback(v.id)}>Rollback</Btn>}
              {v.draft && <Btn variant="primary" size="sm">Apply</Btn>}
            </div>
            <div style={{ fontSize: 12, color: 'var(--p-color-text)', marginBottom: 2 }}>{v.label}</div>
            <div style={{ fontSize: 11, color: 'var(--p-color-text-secondary)' }}>{v.ts} · Sarah Nguyễn</div>
          </Card>
        ))}
      </div>
    </div>
  );
}

function SettingsPane() {
  return (
    <div className="p-scroll" style={{ flex: 1, overflow: 'auto', padding: 14 }}>
      <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 12 }}>App settings</div>
      {[
        { label: 'App name', value: 'Coupon Manager', editable: true },
        { label: 'Icon', value: 'C · gradient' },
        { label: 'Visible in', value: 'Operations · Storefront' },
        { label: 'Scopes', value: 'discounts · customers (read)' },
        { label: 'Auto-deploy', value: 'On apply only' },
      ].map((s) => (
        <div key={s.label} style={{
          padding: '10px 0', borderBottom: '1px solid var(--p-color-border-secondary)',
          display: 'flex', alignItems: 'center', gap: 10,
        }}>
          <div style={{ fontSize: 12, color: 'var(--p-color-text-secondary)', flex: 1 }}>{s.label}</div>
          <div style={{ fontSize: 12.5, color: 'var(--p-color-text)' }}>{s.value}</div>
          {s.editable && <Btn variant="plain" size="sm" icon={I.edit} />}
        </div>
      ))}
      <div style={{ marginTop: 14, padding: 12, background: 'var(--p-color-bg-fill-critical)', borderRadius: 8, fontSize: 12, color: 'var(--p-color-text-critical)' }}>
        <b>Danger zone</b><br />
        <span>Delete this app · removes it from Operations and unpublishes any related widgets.</span>
        <div style={{ marginTop: 8 }}>
          <Btn variant="critical" size="sm">Delete app</Btn>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { RouteEditor, CouponManagerPreview, ChatPane, VersionsPane, SettingsPane });

// ─── Storefront preview rendered inside the editor ────────
// Wraps the registry's Storefront component in a centered card with
// the in-context "edit on storefront" affordance. Point-click is
// disabled here (route the user to the Storefront edit mode for that).
function EditorStorefrontPreview({ appName, appMeta }) {
  const Comp = appMeta?.Storefront || (window.getPreview && window.getPreview(appName)?.Storefront);
  const wrapRef = React.useRef(null);
  const [scale, setScale] = React.useState(1);

  React.useLayoutEffect(() => {
    if (!wrapRef.current) return;
    const el = wrapRef.current;
    const ro = new ResizeObserver(() => {
      const w = el.clientWidth;
      // 720 is the native preview width; cap scale at 1.15 so it never balloons
      setScale(Math.min(1.15, Math.max(0.4, w / 720)));
    });
    ro.observe(el);
    return () => ro.disconnect();
  }, [Comp]);

  if (!Comp) {
    return (
      <div style={{
        maxWidth: 900, margin: '40px auto',
        padding: 32, textAlign: 'center',
        background: 'var(--p-color-bg-surface)',
        border: '1px dashed var(--p-color-border)',
        borderRadius: 12,
        color: 'var(--p-color-text-secondary)',
      }}>
        <I.store size={22} />
        <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--p-color-text)', marginTop: 8 }}>
          {appName} is admin-only
        </div>
        <div style={{ fontSize: 12.5, marginTop: 6, maxWidth: 360, marginLeft: 'auto', marginRight: 'auto', lineHeight: 1.5 }}>
          This app doesn't render on the storefront. Flip back to the <b>Admin</b> tab to keep editing — or open the Storefront route to edit other widgets.
        </div>
      </div>
    );
  }
  return (
    <div ref={wrapRef} style={{ maxWidth: 900, margin: '0 auto' }}>
      <div style={{
        background: 'var(--p-color-bg-surface)',
        border: '1px solid var(--p-color-border)',
        borderRadius: 12, overflow: 'hidden',
        boxShadow: '0 8px 24px rgba(0,0,0,0.06)',
        width: 720 * scale,
        height: 440 * scale,
        margin: '0 auto',
        position: 'relative',
      }}>
        <div style={{
          width: 720, height: 440,
          transform: `scale(${scale})`, transformOrigin: 'top left',
          position: 'absolute', top: 0, left: 0,
        }}>
          <Comp />
        </div>
      </div>
      <div style={{
        marginTop: 12, padding: '10px 14px',
        background: 'var(--p-color-bg-fill-magic)',
        color: 'var(--p-color-text-magic)',
        borderRadius: 8, fontSize: 12, lineHeight: 1.5,
        display: 'flex', alignItems: 'flex-start', gap: 10,
      }}>
        <I.sparkle size={14} />
        <div style={{ flex: 1 }}>
          <b>This is how customers see {appName} on sarahslinen.com.</b><br />
          Showing fake storefront data. Edits made in the chat sidebar update both this preview and the storefront once you Apply.
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { EditorStorefrontPreview });
