// Surface — Layout library
// Gallery of all layout types. Authors browse to discover what's possible
// (read-only). Admins manage the library and generate new types via AI.

const LIBRARY_META = {
  fullscreen_video:          { use: 'Anchor a moment with edge-to-edge video.',
                               options: ['Source upload / URL', 'Captions', 'Autoplay', 'Required watch'] },
  fullscreen_text_and_image: { use: 'Big hero: one image, one statement.',
                               options: ['Image', 'Title', 'Subtitle', 'Alignment'] },
  text_and_image:            { use: 'Alternating rows of image + paragraph for explainers.',
                               options: ['Rows (1–6)', 'Image / text per row', 'Side', 'Caption'] },
  small_video:               { use: 'Inline video with supporting copy beside it.',
                               options: ['Video', 'Title', 'Body', 'Position'] },
  sequence:                  { use: 'Step-by-step tabbed flow with optional branching.',
                               options: ['Tabs (2–8)', 'Per-tab media', 'Branching', 'Inline question'] },
  icons_discover:            { use: 'Grid of icons the learner taps to reveal more.',
                               options: ['Items (3–9)', 'Icon · title · reveal', 'Required taps'] },
  vertical_tabs:             { use: 'Sidebar tabs with content panel — good for reference.',
                               options: ['Tabs', 'Per-tab body', 'Default tab'] },
  horizontal_tabs:           { use: 'Top tabs with content below — light comparison.',
                               options: ['Tabs', 'Per-tab body', 'Default tab'] },
  hidden_items_flat_image:   { use: 'Tap to reveal — hotspots over a flat image.',
                               options: ['Background image', 'Hotspots', 'Reveal panel'] },
  hidden_items_360_image:    { use: 'Tap to reveal — hotspots inside a 360° panorama.',
                               options: ['Panorama', '"Drag to look" prompt', 'Hotspots', 'Reveal panel'] },
  mandatoryQuestion:         { use: 'Blocking question — learner must answer to proceed.',
                               options: ['Stem', 'Options', 'Correct answer', 'Feedback'] },
  question:                  { use: 'Inline question that does not block progression.',
                               options: ['Stem', 'Options', 'Correct answer', 'Hint'] },
  quiz_images:               { use: 'A sequence of questions with feedback — not tied to course completion.',
                               options: ['Questions', 'Answers', 'Feedback'] },
  quiz_gaming:               { use: 'A gamified quiz with start/end screens and a score counter — can complete the course.',
                               options: ['Questions', 'Answers', 'Counter header', 'Start/end screens'] },
  title:                     { use: 'Section divider with big title — orients the learner.',
                               options: ['Title', 'Subtitle', 'Background'] },
  two_columns_text:          { use: 'Two columns of copy — compare or contrast.',
                               options: ['Column A · B', 'Headings', 'Body'] },
  object_viewer:             { use: 'Interactive 3D model the learner can rotate.',
                               options: ['Model file', 'Hotspots', 'Default angle'] },
};

const LIBRARY_GROUP_ORDER = ['Hero', 'Content', 'Video', 'Interactive', 'Assessment'];
const LIBRARY_GROUP_BLURB = {
  Hero:        'Big, scene-setting layouts. Use sparingly.',
  Content:     'Explain, describe, walk through.',
  Video:       'Anchor a key moment in motion.',
  Interactive: 'Learner-driven exploration and reveal.',
  Assessment:  'Check understanding — blocking and non-blocking.',
};

function SurfaceLayoutLibrary({ viewAs = 'author', onChangeViewAs }) {
  const [generateOpen, setGenerateOpen] = React.useState(false);
  const [manageTemplates, setManageTemplates] = React.useState(false);
  const isAdmin = viewAs === 'admin';
  const types = (window.LAYOUT_TYPES || []).filter(t => !t.hidden);

  // Group + order
  const grouped = {};
  types.forEach(t => { (grouped[t.group] = grouped[t.group] || []).push(t); });
  const orderedGroups = LIBRARY_GROUP_ORDER.filter(g => grouped[g]);

  // Manage-templates sub-screen (Fix B) — a full-surface route off the
  // Quiz · gaming card. Exposed unconditionally (role gating is out of scope).
  if (manageTemplates) {
    return <ManageTemplatesScreen onBack={() => setManageTemplates(false)} />;
  }

  return (
    <div style={{
      height: '100%',
      background: 'var(--bg)',
      overflow: 'auto',
      display: 'flex', flexDirection: 'column', minHeight: 0,
    }} data-screen-label="Surface · Layout library">

      <LibraryHeader
        count={types.length}
        viewAs={viewAs}
        onChangeViewAs={onChangeViewAs}
        onGenerate={() => setGenerateOpen(true)} />

      <div style={{ padding: '20px 28px 48px', display: 'flex', flexDirection: 'column', gap: 28 }}>
        {!isAdmin && <AuthorIntro />}
        {isAdmin && <AdminIntro onGenerate={() => setGenerateOpen(true)} />}

        {orderedGroups.map(g => (
          <LibraryGroup key={g} group={g} items={grouped[g]} isAdmin={isAdmin}
            onManageTemplates={() => setManageTemplates(true)} />
        ))}
      </div>

      {generateOpen && <GenerateLayoutModal onClose={() => setGenerateOpen(false)} />}
    </div>
  );
}

// ── Header ──────────────────────────────────────────────────────────────────
function LibraryHeader({ count, viewAs, onChangeViewAs, onGenerate }) {
  const isAdmin = viewAs === 'admin';
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 12,
      padding: '14px 28px',
      borderBottom: '1px solid var(--border)',
      background: 'var(--surface)',
      minHeight: 56,
      position: 'sticky', top: 0, zIndex: 5,
    }}>
      <I.Grid size={16} style={{ color: 'var(--text-muted)' }} />
      <h2 style={{ margin: 0, fontSize: 14, fontWeight: 600, letterSpacing: '-.005em' }}>Layout library</h2>
      <span style={{
        fontSize: 11, color: 'var(--text-faint)',
        fontFamily: 'var(--font-mono)',
        background: 'var(--surface-inset)', padding: '1px 6px', borderRadius: 3,
      }}>{count} types</span>

      <div style={{ flex: 1 }} />

      {/* Persona toggle — prototype affordance, not a real product control */}
      <div style={{
        display: 'inline-flex', alignItems: 'center', gap: 0,
        padding: 2, borderRadius: 'var(--radius)',
        background: 'var(--surface-inset)', border: '1px solid var(--border)',
      }} title="Prototype: switch persona to see the admin variant">
        <span style={{ fontSize: 10.5, color: 'var(--text-faint)',
          padding: '0 8px 0 6px', letterSpacing: '.04em', textTransform: 'uppercase', fontWeight: 600 }}>
          View as
        </span>
        {['author', 'admin'].map(role => {
          const active = viewAs === role;
          return (
            <button key={role} onClick={() => onChangeViewAs && onChangeViewAs(role)}
              style={{
                height: 22, padding: '0 10px',
                border: 0, borderRadius: 3,
                background: active ? 'var(--surface)' : 'transparent',
                color: active ? 'var(--text)' : 'var(--text-muted)',
                fontSize: 11.5, fontWeight: active ? 600 : 500, fontFamily: 'inherit',
                cursor: 'default',
                boxShadow: active ? 'var(--shadow-sm)' : 'none',
                display: 'inline-flex', alignItems: 'center', gap: 5,
              }}>
              {role === 'admin' && <I.Lock size={11} />}
              {role[0].toUpperCase() + role.slice(1)}
            </button>
          );
        })}
      </div>

      {isAdmin && (
        <button className="btn sm primary" onClick={onGenerate}>
          <I.Sparkle size={13} />Generate layout
        </button>
      )}
    </div>
  );
}

// ── Intro blocks ────────────────────────────────────────────────────────────
function AuthorIntro() {
  return (
    <div style={{
      padding: '14px 16px',
      background: 'var(--surface)',
      border: '1px solid var(--border)',
      borderRadius: 'var(--radius-md)',
      display: 'flex', gap: 12, alignItems: 'flex-start',
    }}>
      <div style={{ width: 32, height: 32, borderRadius: 8,
        background: 'var(--accent-bg)', color: 'var(--accent-text)',
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
        <I.Eye size={15} />
      </div>
      <div style={{ minWidth: 0 }}>
        <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 2 }}>What can you do with a layout?</div>
        <div style={{ fontSize: 12.5, color: 'var(--text-muted)', lineHeight: 1.5, maxWidth: 720 }}>
          Browse every layout type your course can use. Each card shows the structure, what it's
          good for, and the options you'll see when editing one in <strong>Course architecture</strong>.
          This view is read-only — to use a layout, open a module from Course architecture.
        </div>
      </div>
    </div>
  );
}

function AdminIntro({ onGenerate }) {
  return (
    <div style={{
      padding: '14px 16px',
      background: 'var(--surface)',
      border: '1px solid var(--border)',
      borderRadius: 'var(--radius-md)',
      display: 'flex', gap: 12, alignItems: 'flex-start',
    }}>
      <div style={{ width: 32, height: 32, borderRadius: 8,
        background: 'color-mix(in oklab, var(--accent) 14%, transparent)', color: 'var(--accent)',
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
        <I.Sparkle size={15} />
      </div>
      <div style={{ minWidth: 0, flex: 1 }}>
        <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 2 }}>Manage the layout library</div>
        <div style={{ fontSize: 12.5, color: 'var(--text-muted)', lineHeight: 1.5, maxWidth: 720 }}>
          Inspect the option schema for each layout type and add new ones. Drop a URL, image,
          or short brief — the AI proposes a layout type, options, and a preview, then adds it
          to the library for all authors.
        </div>
      </div>
      <button className="btn sm primary" onClick={onGenerate} style={{ flexShrink: 0 }}>
        <I.Plus size={13} />New layout
      </button>
    </div>
  );
}

// ── Group section ───────────────────────────────────────────────────────────
function LibraryGroup({ group, items, isAdmin, onManageTemplates }) {
  return (
    <section>
      <header style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginBottom: 12 }}>
        <h3 style={{ margin: 0, fontSize: 12, fontWeight: 600,
          letterSpacing: '.06em', textTransform: 'uppercase', color: 'var(--text-faint)' }}>
          {group}
        </h3>
        <span style={{ fontSize: 11, color: 'var(--text-faint)',
          fontFamily: 'var(--font-mono)' }}>{items.length}</span>
        <span style={{ flex: 1, height: 1, background: 'var(--border)', marginLeft: 4 }} />
        <span style={{ fontSize: 11.5, color: 'var(--text-faint)' }}>
          {LIBRARY_GROUP_BLURB[group]}
        </span>
      </header>
      <div style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))',
        gap: 14,
      }}>
        {items.map(t => (
          <LayoutCard key={t.id} type={t} isAdmin={isAdmin}
            onManageTemplates={onManageTemplates} />
        ))}
      </div>
    </section>
  );
}

// ── Layout card ─────────────────────────────────────────────────────────────
function LayoutCard({ type, isAdmin, onManageTemplates }) {
  const meta = LIBRARY_META[type.id] || { use: '', options: [] };
  const Icon = I[type.icon] || I.Hash;
  // The Quiz · gaming card carries the entry point into the HTML template
  // library (Fix B). Available regardless of persona.
  const hasTemplates = type.id === 'quiz_gaming';
  return (
    <article style={{
      background: 'var(--surface)',
      border: '1px solid var(--border)',
      borderRadius: 'var(--radius-md)',
      overflow: 'hidden',
      display: 'flex', flexDirection: 'column',
      transition: 'border-color 120ms, box-shadow 120ms',
    }}
    onMouseOver={e => { e.currentTarget.style.borderColor = 'var(--border-strong)';
                        e.currentTarget.style.boxShadow = 'var(--shadow-sm)'; }}
    onMouseOut={e =>  { e.currentTarget.style.borderColor = 'var(--border)';
                        e.currentTarget.style.boxShadow = 'none'; }}>

      <LayoutPreview type={type.id} />

      <div style={{ padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 8, flex: 1 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
          <Icon size={13} style={{ color: 'var(--text-muted)', flexShrink: 0 }} />
          <span style={{ fontSize: 13, fontWeight: 600, minWidth: 0,
            overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
            title={type.label}>{type.label}</span>
        </div>
        <div style={{ fontFamily: 'var(--font-mono)', fontSize: 10.5,
          color: 'var(--text-faint)', marginTop: -4 }}>
          {type.id}
        </div>

        <p style={{ margin: 0, fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.5 }}>
          {meta.use}
        </p>

        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 'auto', paddingTop: 4 }}>
          {meta.options.map(o => (
            <span key={o} style={{
              fontSize: 10.5, padding: '2px 6px',
              background: 'var(--surface-inset)',
              color: 'var(--text-muted)',
              borderRadius: 3, fontFamily: 'inherit',
            }}>{o}</span>
          ))}
        </div>

        {hasTemplates && (
          <button onClick={onManageTemplates} style={{
            display: 'inline-flex', alignItems: 'center', gap: 6, alignSelf: 'flex-start',
            marginTop: 4, padding: '5px 8px', border: '1px solid var(--accent-border)',
            borderRadius: 'var(--radius)', background: 'var(--accent-bg)',
            color: 'var(--accent-text)', fontSize: 11.5, fontWeight: 600,
            fontFamily: 'inherit', cursor: 'default',
          }}
          title={`${(window.HTML_TEMPLATES || []).length} HTML templates`}>
            <I.Grid size={12} />Manage templates
            <I.ArrowRight size={12} />
          </button>
        )}

        {isAdmin && (
          <div style={{ display: 'flex', gap: 6, marginTop: 4, paddingTop: 8,
            borderTop: '1px solid var(--border)' }}>
            <button className="btn sm ghost" style={{ flex: 1 }}>
              <I.PenLine size={12} />Edit schema
            </button>
            <button className="btn sm ghost" title="Duplicate as starting point for a new type">
              <I.Copy size={12} />
            </button>
          </div>
        )}
      </div>
    </article>
  );
}

// ── Mini player preview per type ───────────────────────────────────────────
// These thumbnails are deliberately close to the REAL rendered output the
// Dynamo Player produces (see preview-renderers.jsx) — same background tones,
// centred title bars, play buttons, tab strips, step dots, hotspot pins and
// answer chips — rendered at wireframe fidelity (grey/striped placeholders for
// real media). The goal: an author recognises a layout from its card alone.
function LayoutPreview({ type }) {
  // Full-bleed mini "screen" filling the card header.
  const screen = (bg, children) => (
    <div style={{
      height: 134, background: bg, borderBottom: '1px solid var(--border)',
      position: 'relative', overflow: 'hidden', display: 'flex', flexDirection: 'column',
    }}>{children}</div>
  );

  // Centred title bar (title line + optional subtitle line) — mirrors PlayerTitleBar.
  const titleBar = (color = 'rgba(255,255,255,.92)', { sub = true, pad = '12px 0 9px' } = {}) => (
    <div style={{ padding: pad, display: 'flex', flexDirection: 'column',
      alignItems: 'center', gap: 4, flexShrink: 0 }}>
      <div style={{ height: 5, width: '48%', borderRadius: 2, background: color }} />
      {sub && <div style={{ height: 3, width: '30%', borderRadius: 2, background: color, opacity: 0.5 }} />}
    </div>
  );

  // White play disc with a dark triangle — the player's video affordance.
  const play = (s = 26) => (
    <div style={{ width: s, height: s, borderRadius: '50%', background: 'rgba(255,255,255,.92)',
      display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
      boxShadow: '0 2px 8px rgba(0,0,0,.35)', flexShrink: 0 }}>
      <div style={{ width: 0, height: 0,
        borderLeft: `${Math.round(s * 0.32)}px solid #0f172a`,
        borderTop: `${Math.round(s * 0.2)}px solid transparent`,
        borderBottom: `${Math.round(s * 0.2)}px solid transparent`,
        marginLeft: Math.round(s * 0.14) }} />
    </div>
  );

  // A stack of text lines (widths array). `color` + base opacity per line.
  const lines = (widths, color, gap = 4) => (
    <div style={{ display: 'flex', flexDirection: 'column', gap }}>
      {widths.map((w, i) => (
        <div key={i} style={{ height: 3.5, width: w, borderRadius: 2,
          background: color, opacity: 0.9 - i * 0.12 }} />
      ))}
    </div>
  );

  // Striped placeholder for a photo/image region (theme-aware tone).
  const imgStripe = {
    backgroundColor: 'var(--surface-inset)',
    backgroundImage: 'repeating-linear-gradient(135deg, rgba(100,116,139,.20) 0 5px, rgba(100,116,139,.06) 5px 10px)',
  };
  // Dark photo region (for hidden-items backgrounds over dark scenes).
  const imgDark = {
    backgroundColor: '#1e293b',
    backgroundImage: 'repeating-linear-gradient(135deg, rgba(255,255,255,.10) 0 5px, rgba(255,255,255,.03) 5px 10px)',
  };
  // A hotspot pin: white disc, coloured ring + "+".
  const pin = (color, left, top, extra = {}) => (
    <div style={{ position: 'absolute', left, top, transform: 'translate(-50%,-50%)',
      width: 15, height: 15, borderRadius: '50%', background: 'rgba(255,255,255,.92)',
      border: `2px solid ${color}`, color, fontSize: 10, fontWeight: 700, lineHeight: 1,
      display: 'inline-flex', alignItems: 'center', justifyContent: 'center', ...extra }}>+</div>
  );

  // ── Thumbnail palette ──────────────────────────────────────────────────
  // The whole library reads as one family: STRUCTURE differentiates the
  // layouts, colour does not. So there are exactly three scene tones (all
  // from the slate neutral ramp) plus ONE highlight (the system accent) used
  // for every active / selected / interactive affordance — current step dot,
  // active tab, selected answer, reveal hotspot. No per-layout colours.
  const INK = '#0f172a';          // single dark scene for every immersive layout
  const VIDEO = '#020617';        // the video surface itself — one level deeper than INK
  const ACCENT = 'var(--accent)'; // the one highlight: active / selected / interactive

  switch (type) {

    // ── Video: black scene, title bar, 16:9 video + progress ───────────────
    case 'fullscreen_video':
      return screen(INK, (
        <>
          {titleBar('rgba(255,255,255,.85)')}
          <div style={{ flex: 1, minHeight: 0, display: 'flex', alignItems: 'center',
            justifyContent: 'center', padding: '0 18px 14px' }}>
            <div style={{ width: '100%', maxWidth: 200, aspectRatio: '16/9', background: VIDEO,
              position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              {play(28)}
              {pin(ACCENT, '74%', '38%')}
              <div style={{ position: 'absolute', left: 8, right: 8, bottom: 7, height: 2.5,
                borderRadius: 2, background: 'rgba(255,255,255,.25)' }}>
                <div style={{ width: '34%', height: '100%', background: '#fff', borderRadius: 2 }} />
              </div>
            </div>
          </div>
        </>
      ));

    // ── Hero: dark slate, big centred statement ────────────────────────────
    case 'fullscreen_text_and_image':
      return screen(INK, (
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column',
          alignItems: 'center', justifyContent: 'center', gap: 8, padding: '0 26px' }}>
          <div style={{ height: 9, width: '68%', borderRadius: 2, background: 'rgba(255,255,255,.92)' }} />
          <div style={{ display: 'flex', flexDirection: 'column', gap: 4, alignItems: 'center', width: '100%' }}>
            <div style={{ height: 3.5, width: '60%', borderRadius: 2, background: 'rgba(255,255,255,.55)' }} />
            <div style={{ height: 3.5, width: '44%', borderRadius: 2, background: 'rgba(255,255,255,.4)' }} />
          </div>
        </div>
      ));

    // ── Text + image rows: white scene, alternating image / paragraph ──────
    case 'text_and_image':
      return screen('#fff', (
        <>
          {titleBar('var(--text-muted)', { sub: false, pad: '9px 0 7px' })}
          <div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
            {[0, 1].map(i => (
              <div key={i} style={{ flex: 1, display: 'grid', gridTemplateColumns: '1fr 1fr',
                borderTop: i ? '1px solid var(--border)' : 0 }}>
                <div style={{ ...imgStripe, order: i % 2 ? 1 : 0 }} />
                <div style={{ display: 'flex', alignItems: 'center', padding: '0 12px' }}>
                  {lines(['100%', '100%', '60%'], 'var(--border-strong)', 3.5)}
                </div>
              </div>
            ))}
          </div>
        </>
      ));

    // ── Small video: light scene, title, centred shadowed video ────────────
    case 'small_video':
      return screen('#eef2f6', (
        <>
          {titleBar('var(--text-muted)')}
          <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center',
            padding: '0 0 16px' }}>
            <div style={{ height: 64, aspectRatio: '16/9', background: VIDEO,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              boxShadow: '0 5px 14px rgba(0,0,0,.22)' }}>{play(22)}</div>
          </div>
        </>
      ));

    // ── Sequence: black scene, image canvas + floating card + step dots ────
    case 'sequence':
      return screen(INK, (
        <>
          {titleBar('rgba(255,255,255,.85)', { pad: '10px 0 7px' })}
          <div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column',
            alignItems: 'center', justifyContent: 'center', gap: 9, padding: '0 20px 9px' }}>
            <div style={{ width: '100%', flex: 1, minHeight: 0, ...imgDark, position: 'relative',
              display: 'flex', alignItems: 'flex-end' }}>
              <div style={{ margin: '0 10px 8px', background: '#fff', borderRadius: 2,
                padding: '6px 7px', width: '62%', display: 'flex', flexDirection: 'column', gap: 3 }}>
                {lines(['100%', '78%'], 'var(--border-strong)', 3)}
              </div>
            </div>
            <div style={{ display: 'flex', gap: 5, flexShrink: 0 }}>
              {[0, 1, 2, 3, 4].map(n => (
                <div key={n} style={{ width: 7, height: 7, borderRadius: '50%',
                  background: n === 1 ? ACCENT : 'rgba(255,255,255,.3)' }} />
              ))}
            </div>
          </div>
        </>
      ));

    // ── Horizontal tabs: white scene, accent tab strip with arrows + body ────
    case 'horizontal_tabs':
      return screen('#fff', (
        <>
          {titleBar('var(--text-muted)', { sub: false, pad: '9px 0 7px' })}
          <div style={{ flex: 1, minHeight: 0, padding: '0 12px 11px',
            display: 'flex', flexDirection: 'column', gap: 9 }}>
            <div style={{ display: 'flex', gap: 3, alignItems: 'stretch', justifyContent: 'center' }}>
              <div style={{ width: 13, background: ACCENT, borderRadius: 1,
                display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                <I.ChevronLeft size={9} style={{ color: '#fff' }} />
              </div>
              {[0, 1, 2].map(i => (
                <div key={i} style={{ flex: 1, height: 19, borderRadius: 1,
                  background: i === 0 ? ACCENT : '#f1f5f9' }} />
              ))}
              <div style={{ width: 13, background: ACCENT, borderRadius: 1,
                display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                <I.ChevronRight size={9} style={{ color: '#fff' }} />
              </div>
            </div>
            <div style={{ paddingLeft: 2 }}>{lines(['100%', '92%', '54%'], 'var(--border-strong)')}</div>
          </div>
        </>
      ));

    // ── Vertical tabs: ink scene, white tab list + accent active + white panel ─
    case 'vertical_tabs':
      return screen(INK, (
        <>
          {titleBar('rgba(255,255,255,.92)', { sub: false, pad: '9px 0 7px' })}
          <div style={{ flex: 1, minHeight: 0, display: 'grid', gridTemplateColumns: '34% 1fr',
            gap: 8, padding: '0 12px 12px' }}>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
              {[0, 1, 2].map(i => (
                <div key={i} style={{ flex: 1, borderRadius: 1,
                  background: i === 0 ? ACCENT : '#fff' }} />
              ))}
            </div>
            <div style={{ background: '#fff', padding: '8px 9px', display: 'flex',
              flexDirection: 'column', gap: 5, justifyContent: 'center' }}>
              <div style={{ height: 5, width: '56%', borderRadius: 2, background: 'var(--text-muted)' }} />
              {lines(['100%', '100%', '70%'], 'var(--border-strong)')}
            </div>
          </div>
        </>
      ));

    // ── Icons discover: ink scene, copy + tappable icon grid ───────────────
    case 'icons_discover':
      return screen(INK, (
        <>
          {titleBar('rgba(255,255,255,.92)', { sub: false, pad: '9px 0 7px' })}
          <div style={{ flex: 1, minHeight: 0, display: 'grid', gridTemplateColumns: '1fr 1fr',
            gap: 10, padding: '0 14px 12px', alignItems: 'center' }}>
            {lines(['100%', '90%', '76%', '50%'], 'rgba(255,255,255,.6)')}
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
              {[0, 1, 2, 3].map(i => (
                <div key={i} style={{ aspectRatio: '2 / 1', borderRadius: 2,
                  background: i === 1 ? ACCENT : 'transparent',
                  border: `1px solid ${i === 1 ? ACCENT : 'rgba(255,255,255,.85)'}`,
                  display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                  <div style={{ width: 9, height: 9, borderRadius: '50%',
                    border: '1.5px solid #fff' }} />
                </div>
              ))}
            </div>
          </div>
        </>
      ));

    // ── Hidden items: photo / panorama with reveal hotspots ────────────────
    case 'hidden_items':
    case 'hidden_items_flat_image':
    case 'hidden_items_360_image': {
      const is360 = type === 'hidden_items_360_image';
      return screen(INK, (
        <>
          {titleBar('rgba(255,255,255,.92)', { sub: false, pad: '9px 0 7px' })}
          <div style={{ flex: 1, minHeight: 0, margin: '0 14px 12px', position: 'relative',
            ...imgDark }}>
            {pin(ACCENT, '28%', '40%')}
            {pin(ACCENT, '62%', '60%')}
            {pin(ACCENT, '80%', '32%')}
            {is360 && (
              <div style={{ position: 'absolute', left: 6, bottom: 5, padding: '2px 6px',
                borderRadius: 999, border: '1px solid rgba(255,255,255,.7)',
                color: 'rgba(255,255,255,.85)', fontSize: 8, fontWeight: 600,
                fontFamily: 'var(--font-mono)' }}>360°</div>
            )}
          </div>
        </>
      ));
    }

    // ── Quiz (sequence of questions): dark scene, prompt + answer chips ────
    case 'quiz_images':
      return screen(INK, (
        <>
          {titleBar('rgba(255,255,255,.85)', { sub: false, pad: '10px 0 7px' })}
          <div style={{ flex: 1, minHeight: 0, padding: '0 16px 12px', display: 'flex',
            flexDirection: 'column', gap: 7, justifyContent: 'center' }}>
            <div style={{ height: 4.5, width: '82%', borderRadius: 2, background: 'rgba(255,255,255,.9)' }} />
            {[0, 1, 2].map(i => (
              <div key={i} style={{ height: 14, borderRadius: 2,
                background: i === 1 ? ACCENT : 'transparent',
                border: `1px solid ${i === 1 ? ACCENT : 'rgba(255,255,255,.55)'}` }} />
            ))}
          </div>
        </>
      ));

    // ── Gamified quiz: dark scene, score counter header + answers ──────────
    case 'quiz_gaming':
      return screen(INK, (
        <>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
            padding: '9px 12px 7px', flexShrink: 0 }}>
            <div style={{ height: 4.5, width: '40%', borderRadius: 2, background: 'rgba(255,255,255,.7)' }} />
            <div style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '2px 7px',
              borderRadius: 999, background: 'color-mix(in oklab, var(--accent) 18%, transparent)',
              border: '1px solid color-mix(in oklab, var(--accent) 55%, transparent)' }}>
              <div style={{ width: 6, height: 6, borderRadius: '50%', background: ACCENT }} />
              <div style={{ height: 3, width: 14, borderRadius: 2, background: ACCENT }} />
            </div>
          </div>
          <div style={{ flex: 1, minHeight: 0, padding: '0 16px 12px', display: 'flex',
            flexDirection: 'column', gap: 7, justifyContent: 'center' }}>
            <div style={{ height: 4.5, width: '78%', borderRadius: 2, background: 'rgba(255,255,255,.9)' }} />
            {[0, 1].map(i => (
              <div key={i} style={{ height: 16, borderRadius: 2,
                background: i === 0 ? ACCENT : 'transparent',
                border: `1px solid ${i === 0 ? ACCENT : 'rgba(255,255,255,.55)'}` }} />
            ))}
          </div>
        </>
      ));

    // ── Standalone question (blocking / non-blocking) ──────────────────────
    case 'mandatoryQuestion':
    case 'question':
      return screen('#f8fafc', (
        <>
          {titleBar('var(--text-muted)', { sub: false, pad: '9px 0 7px' })}
          <div style={{ flex: 1, minHeight: 0, display: 'flex', alignItems: 'center',
            justifyContent: 'center', padding: '0 16px 12px' }}>
            <div style={{ width: '100%', background: '#fff', boxShadow: '0 4px 12px rgba(0,0,0,.07)',
              padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: 7 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
                <div style={{ height: 5, width: '70%', borderRadius: 2, background: 'var(--text-muted)' }} />
                {type === 'mandatoryQuestion' && <I.Lock size={9} style={{ color: 'var(--warning)' }} />}
              </div>
              {[0, 1, 2].map(i => (
                <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                  <div style={{ width: 9, height: 9, borderRadius: '50%', flexShrink: 0,
                    border: '1.5px solid var(--border-strong)',
                    background: i === 1 ? 'var(--accent)' : 'transparent' }} />
                  <div style={{ height: 3.5, width: `${78 - i * 12}%`, borderRadius: 2,
                    background: 'var(--border-strong)' }} />
                </div>
              ))}
            </div>
          </div>
        </>
      ));

    // ── Title divider: dark scene, large centred title + subtitle ──────────
    case 'title':
      return screen(INK, (
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column',
          alignItems: 'center', justifyContent: 'center', gap: 7, padding: '0 24px' }}>
          <div style={{ height: 11, width: '60%', borderRadius: 2, background: '#fff' }} />
          <div style={{ height: 5, width: '38%', borderRadius: 2, background: 'rgba(255,255,255,.55)' }} />
        </div>
      ));

    // ── Two columns: paired neutral columns of copy ───────────────────────
    case 'two_columns_text':
      return screen(INK, (
        <div style={{ flex: 1, display: 'grid', gridTemplateColumns: '1fr 1fr' }}>
          {[['#0f172a'], ['#1e293b']].map(([bg], i) => (
            <div key={i} style={{ background: bg, padding: '0 14px',
              display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 5 }}>
              <div style={{ height: 5, width: '64%', borderRadius: 2, background: 'rgba(255,255,255,.85)' }} />
              {lines(['100%', '100%', '70%'], 'rgba(255,255,255,.5)')}
            </div>
          ))}
        </div>
      ));

    // ── Object viewer: dark scene, 3D model + drag-to-explore cue ──────────
    case 'object_viewer':
      return screen(INK, (
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column',
          alignItems: 'center', justifyContent: 'center', gap: 8 }}>
          <svg viewBox="0 0 60 60" width="56" height="56" style={{ color: 'rgba(255,255,255,.55)' }}>
            <path d="M30 6 L52 18 V42 L30 54 L8 42 V18 Z" fill="none" stroke="currentColor" strokeWidth="1.5" />
            <path d="M30 6 V30 M30 30 L52 18 M30 30 L8 18 M30 30 V54" stroke="currentColor" strokeWidth="1" opacity="0.5" />
          </svg>
          <div style={{ padding: '2px 8px', borderRadius: 999, border: '1px solid rgba(255,255,255,.6)',
            color: 'rgba(255,255,255,.8)', fontSize: 8, fontWeight: 600,
            fontFamily: 'var(--font-mono)' }}>360°</div>
        </div>
      ));

    default:
      return screen('var(--surface-inset)', null);
  }
}

// ── Generate-layout modal (admin only) ─────────────────────────────────────
function GenerateLayoutModal({ onClose }) {
  const [mode, setMode] = React.useState('text');
  const [stage, setStage] = React.useState('input'); // input · generating · done
  const [text, setText] = React.useState('');
  const [url, setUrl] = React.useState('');
  const [imgName, setImgName] = React.useState('');

  React.useEffect(() => {
    if (stage === 'generating') {
      const t = setTimeout(() => setStage('done'), 1800);
      return () => clearTimeout(t);
    }
  }, [stage]);

  const canSubmit = (mode === 'text' && text.trim().length > 8) ||
                    (mode === 'url' && url.trim().length > 4) ||
                    (mode === 'image' && imgName);

  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'color-mix(in oklab, var(--text) 35%, transparent)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 50,
      padding: 24,
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        width: 560, maxWidth: '100%',
        background: 'var(--surface)',
        border: '1px solid var(--border)',
        borderRadius: 'var(--radius-md)',
        boxShadow: 'var(--shadow-lg)',
        overflow: 'hidden',
      }}>
        {/* Header */}
        <div style={{ padding: '14px 18px', borderBottom: '1px solid var(--border)',
          display: 'flex', alignItems: 'center', gap: 10 }}>
          <div style={{ width: 26, height: 26, borderRadius: 6,
            background: 'color-mix(in oklab, var(--accent) 14%, transparent)', color: 'var(--accent)',
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
            <I.Sparkle size={13} />
          </div>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 13, fontWeight: 600 }}>Generate a new layout type</div>
            <div style={{ fontSize: 11.5, color: 'var(--text-muted)' }}>
              AI proposes structure, option schema, and a preview · added to the library on accept
            </div>
          </div>
          <button className="btn sm ghost" onClick={onClose}><I.X size={13} /></button>
        </div>

        {stage === 'input' && (
          <div style={{ padding: 18, display: 'flex', flexDirection: 'column', gap: 16 }}>
            {/* Mode tabs */}
            <div style={{ display: 'flex', gap: 6,
              padding: 3, background: 'var(--surface-inset)',
              border: '1px solid var(--border)', borderRadius: 'var(--radius)' }}>
              {[
                { id: 'text',  label: 'Describe',  icon: 'Type' },
                { id: 'url',   label: 'From URL',  icon: 'Link' },
                { id: 'image', label: 'From image',icon: 'Image' },
              ].map(m => {
                const Mi = I[m.icon];
                const active = mode === m.id;
                return (
                  <button key={m.id} onClick={() => setMode(m.id)} style={{
                    flex: 1, height: 28, border: 0, borderRadius: 4,
                    background: active ? 'var(--surface)' : 'transparent',
                    color: active ? 'var(--text)' : 'var(--text-muted)',
                    fontSize: 12, fontWeight: active ? 600 : 500, fontFamily: 'inherit',
                    cursor: 'default', display: 'inline-flex', alignItems: 'center',
                    justifyContent: 'center', gap: 6,
                    boxShadow: active ? 'var(--shadow-sm)' : 'none',
                  }}>
                    <Mi size={12} />{m.label}
                  </button>
                );
              })}
            </div>

            {/* Per-mode input */}
            {mode === 'text' && (
              <div>
                <label style={{ fontSize: 11.5, fontWeight: 600, color: 'var(--text-muted)',
                  display: 'block', marginBottom: 6, letterSpacing: '.02em' }}>
                  Describe the layout you need
                </label>
                <textarea value={text} onChange={e => setText(e.target.value)} rows={5}
                  placeholder="e.g. A 'before / after' slider where learners drag a divider across two images to compare them. Include a caption beneath."
                  style={{
                    width: '100%', boxSizing: 'border-box',
                    background: 'var(--surface)', border: '1px solid var(--border)',
                    borderRadius: 'var(--radius)', padding: '10px 12px',
                    fontSize: 12.5, fontFamily: 'inherit', color: 'var(--text)',
                    resize: 'vertical', minHeight: 96, outline: 'none',
                  }} />
              </div>
            )}
            {mode === 'url' && (
              <div>
                <label style={{ fontSize: 11.5, fontWeight: 600, color: 'var(--text-muted)',
                  display: 'block', marginBottom: 6, letterSpacing: '.02em' }}>
                  URL to reference
                </label>
                <input value={url} onChange={e => setUrl(e.target.value)}
                  placeholder="https://example.com/article-with-the-layout-pattern"
                  style={{
                    width: '100%', boxSizing: 'border-box',
                    background: 'var(--surface)', border: '1px solid var(--border)',
                    borderRadius: 'var(--radius)', padding: '8px 12px', height: 36,
                    fontSize: 12.5, fontFamily: 'inherit', color: 'var(--text)', outline: 'none',
                  }} />
                <div style={{ fontSize: 11, color: 'var(--text-faint)', marginTop: 6 }}>
                  AI will scrape the page and propose the closest matching layout pattern.
                </div>
              </div>
            )}
            {mode === 'image' && (
              <div>
                <label style={{ fontSize: 11.5, fontWeight: 600, color: 'var(--text-muted)',
                  display: 'block', marginBottom: 6, letterSpacing: '.02em' }}>
                  Reference image
                </label>
                <div style={{
                  border: '1.5px dashed var(--border-strong)',
                  borderRadius: 'var(--radius)',
                  padding: 24, textAlign: 'center',
                  background: 'var(--surface-inset)',
                  cursor: 'default',
                }} onClick={() => setImgName('design-reference.png')}>
                  <I.Image size={20} style={{ color: 'var(--text-muted)' }} />
                  <div style={{ fontSize: 12.5, fontWeight: 600, marginTop: 6 }}>
                    {imgName || 'Drop an image, or click to choose'}
                  </div>
                  <div style={{ fontSize: 11, color: 'var(--text-faint)', marginTop: 2 }}>
                    PNG, JPG, or screenshot · up to 8 MB
                  </div>
                </div>
              </div>
            )}

            {/* Footer */}
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 4 }}>
              <span style={{ fontSize: 11, color: 'var(--text-faint)' }}>
                <I.Info size={11} /> Generated layouts become available to all authors in this org.
              </span>
              <div style={{ flex: 1 }} />
              <button className="btn sm ghost" onClick={onClose}>Cancel</button>
              <button className="btn sm primary"
                disabled={!canSubmit}
                style={{ opacity: canSubmit ? 1 : 0.5 }}
                onClick={() => canSubmit && setStage('generating')}>
                <I.Sparkle size={12} />Generate
              </button>
            </div>
          </div>
        )}

        {stage === 'generating' && (
          <div style={{ padding: '36px 18px', display: 'flex', flexDirection: 'column',
            alignItems: 'center', gap: 14, textAlign: 'center' }}>
            <div style={{
              width: 40, height: 40, borderRadius: '50%',
              background: 'color-mix(in oklab, var(--accent) 14%, transparent)',
              display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
              animation: 'pulse 1.4s ease-in-out infinite',
            }}>
              <I.Sparkle size={18} style={{ color: 'var(--accent)' }} />
            </div>
            <div style={{ fontSize: 13, fontWeight: 600 }}>Designing layout…</div>
            <div style={{ fontSize: 11.5, color: 'var(--text-muted)', maxWidth: 360 }}>
              Analyzing reference · inferring options · generating preview
            </div>
            <style>{`@keyframes pulse {
              0%,100%{transform:scale(1);opacity:1}
              50%{transform:scale(1.15);opacity:.7}
            }`}</style>
          </div>
        )}

        {stage === 'done' && (
          <div style={{ padding: 18, display: 'flex', flexDirection: 'column', gap: 14 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              <div style={{ width: 26, height: 26, borderRadius: '50%',
                background: 'var(--success)', color: '#fff',
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
                <I.Check size={14} />
              </div>
              <div>
                <div style={{ fontSize: 13, fontWeight: 600 }}>Layout proposed</div>
                <div style={{ fontSize: 11.5, color: 'var(--text-muted)' }}>
                  Review before adding to the library
                </div>
              </div>
            </div>

            {/* Proposed card */}
            <div style={{ border: '1px solid var(--border)', borderRadius: 'var(--radius-md)',
              overflow: 'hidden' }}>
              <div style={{ height: 90, background: 'var(--surface-inset)',
                borderBottom: '1px solid var(--border)',
                display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
                padding: 12 }}>
                <div style={{ flex: 1, height: '100%', background: 'var(--border-strong)',
                  borderRadius: 4 }} />
                <div style={{ width: 2, height: '60%', background: 'var(--accent)' }} />
                <div style={{ flex: 1, height: '100%', background: 'var(--border-strong)',
                  borderRadius: 4, opacity: 0.5 }} />
              </div>
              <div style={{ padding: '10px 12px' }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                  <span style={{ fontSize: 13, fontWeight: 600 }}>Before / after compare</span>
                  <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10.5,
                    color: 'var(--text-faint)', marginLeft: 'auto' }}>before_after_compare</span>
                </div>
                <p style={{ margin: '4px 0 8px', fontSize: 12, color: 'var(--text-muted)',
                  lineHeight: 1.5 }}>
                  Learner drags a divider across two images to compare states.
                </p>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
                  {['Image A', 'Image B', 'Caption', 'Default position'].map(o => (
                    <span key={o} style={{ fontSize: 10.5, padding: '2px 6px',
                      background: 'var(--surface-inset)', color: 'var(--text-muted)',
                      borderRadius: 3 }}>{o}</span>
                  ))}
                </div>
              </div>
            </div>

            <div style={{ display: 'flex', gap: 8 }}>
              <button className="btn sm" onClick={() => setStage('input')}>
                <I.RefreshCw size={12} />Try again
              </button>
              <div style={{ flex: 1 }} />
              <button className="btn sm ghost" onClick={onClose}>Discard</button>
              <button className="btn sm primary" onClick={onClose}>
                <I.Check size={12} />Add to library
              </button>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

Object.assign(window, { SurfaceLayoutLibrary });

// ═════════════════════════════════════════════════════════════════════════════
// Manage templates — Admin sub-screen (Fix B)
// ═════════════════════════════════════════════════════════════════════════════
// A full-surface route off the Quiz · gaming card. Lists every entry in
// window.HTML_TEMPLATES as a card with a live iframe thumbnail + kebab
// (Edit / Duplicate / Delete). "+ New template" opens the same form with a
// paste-HTML field. All mutations are in-memory only (no persistence — Phase 2);
// the 8 starter templates stay read-only on disk but their metadata is editable
// in the registry. Author-facing copy never says "DFTI" (CLAUDE.md §23.2).

function _slugify(s) {
  return (s || 'template').toLowerCase().trim()
    .replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 48) || 'template';
}

function ManageTemplatesScreen({ onBack }) {
  const ConfirmDialog = window.ConfirmDialog;
  // In-memory working copy of the registry. Kept in sync with the global so
  // the MediaSlot picker (Fix C) sees the same list within a session.
  const [templates, setTemplates] = React.useState(() => (window.HTML_TEMPLATES || []).slice());
  const [editing, setEditing] = React.useState(null);   // template object | { _new:true }
  const [confirmDel, setConfirmDel] = React.useState(null);

  const commit = (next) => { setTemplates(next); window.HTML_TEMPLATES = next; };

  const onSave = (draft) => {
    if (draft._new) {
      const slug = _slugify(draft.name);
      const html = draft._html || '';
      // The only place the prototype "writes" HTML: a Blob URL stands in for
      // the on-disk uploads/html-samples/custom/<slug>.html the real product
      // would persist. previewUrl + renderedHtmlUrl point at it so the iframe
      // renders the pasted markup live.
      const url = html.trim()
        ? URL.createObjectURL(new Blob([html], { type: 'text/html' }))
        : (templates[0] ? templates[0].previewUrl : '');
      const entry = {
        id: 'tpl-' + slug + '-' + Math.random().toString(36).slice(2, 6),
        name: draft.name || 'Untitled template',
        category: draft.category || 'custom',
        complexity: draft.complexity || 'med',
        description: draft.description || '',
        previewUrl: url,
        renderedHtmlUrl: url,
        parameters: [],
        tooltipParams: draft.tooltipParams || [],
        stylesheet: 'sample',
        isCustom: true,
        customPath: 'uploads/html-samples/custom/' + slug + '.html',
        _html: html,
      };
      commit([...templates, entry]);
    } else {
      commit(templates.map(t => t.id === draft.id
        ? { ...t, name: draft.name, description: draft.description,
            category: draft.category, complexity: draft.complexity,
            tooltipParams: draft.tooltipParams || [] }
        : t));
    }
    setEditing(null);
  };

  const onDuplicate = (t) => {
    const copy = { ...t, id: t.id + '-copy-' + Math.random().toString(36).slice(2, 6),
      name: 'Copy of ' + t.name, isCustom: true };
    commit([...templates, copy]);
  };

  const doDelete = () => {
    commit(templates.filter(t => t.id !== confirmDel.id));
    setConfirmDel(null);
  };

  return (
    <div style={{
      height: '100%', background: 'var(--bg)', overflow: 'auto',
      display: 'flex', flexDirection: 'column', minHeight: 0,
    }} data-screen-label="Surface · Layout library · Manage templates">

      {/* Top bar: breadcrumb + new */}
      <div style={{
        display: 'flex', alignItems: 'center', gap: 10, padding: '14px 28px',
        borderBottom: '1px solid var(--border)', background: 'var(--surface)',
        minHeight: 56, position: 'sticky', top: 0, zIndex: 5,
      }}>
        <button className="btn sm ghost" onClick={onBack} title="Back to layout library">
          <I.ArrowLeft size={14} />
        </button>
        <nav style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 13, minWidth: 0 }}>
          <button onClick={onBack} style={{ border: 0, background: 'transparent', padding: 0,
            color: 'var(--text-muted)', fontFamily: 'inherit', fontSize: 13, cursor: 'default' }}>
            Layout library
          </button>
          <I.ChevronRight size={12} style={{ color: 'var(--text-faint)', flexShrink: 0 }} />
          <button onClick={onBack} style={{ border: 0, background: 'transparent', padding: 0,
            color: 'var(--text-muted)', fontFamily: 'inherit', fontSize: 13, cursor: 'default' }}>
            Quiz · gaming
          </button>
          <I.ChevronRight size={12} style={{ color: 'var(--text-faint)', flexShrink: 0 }} />
          <span style={{ fontWeight: 600 }}>Manage templates</span>
        </nav>
        <span style={{ fontSize: 11, color: 'var(--text-faint)', fontFamily: 'var(--font-mono)',
          background: 'var(--surface-inset)', padding: '1px 6px', borderRadius: 3 }}>
          {templates.length}
        </span>
        <div style={{ flex: 1 }} />
        <button className="btn sm primary" onClick={() => setEditing({ _new: true })}>
          <I.Plus size={13} />New template
        </button>
      </div>

      <div style={{ padding: '20px 28px 48px', display: 'flex', flexDirection: 'column', gap: 16 }}>
        <div style={{
          padding: '12px 14px', background: 'var(--surface)', border: '1px solid var(--border)',
          borderRadius: 'var(--radius-md)', display: 'flex', gap: 10, alignItems: 'flex-start',
        }}>
          <I.Info size={14} style={{ color: 'var(--text-muted)', marginTop: 1, flexShrink: 0 }} />
          <div style={{ fontSize: 12.5, color: 'var(--text-muted)', lineHeight: 1.5, maxWidth: 760 }}>
            HTML templates back the <strong>HTML document</strong> media kind on Quiz · gaming
            questions. Authors pick one when building a question. The eight starter templates are
            read-only on disk — editing here updates their library metadata only. Changes in this
            scaffold are not yet saved between sessions.
          </div>
        </div>

        <div style={{
          display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: 16,
        }}>
          {templates.map(t => (
            <ManageTemplateCard key={t.id} template={t}
              onEdit={() => setEditing(t)}
              onDuplicate={() => onDuplicate(t)}
              onDelete={() => setConfirmDel(t)} />
          ))}
        </div>
      </div>

      {editing && (
        <TemplateEditModal template={editing}
          onSave={onSave} onClose={() => setEditing(null)} />
      )}
      {confirmDel && (
        <ConfirmDialog
          icon="Trash" tone="danger"
          title={`Delete “${confirmDel.name}”?`}
          body="This removes the template from the library. Questions already using it keep their embedded HTML. This can't be undone."
          confirmLabel="Delete template"
          onCancel={() => setConfirmDel(null)} onConfirm={doDelete} />
      )}
    </div>
  );
}

// Small badge — category / complexity chip.
function TemplateChip({ children, tone = 'neutral' }) {
  const tones = {
    neutral: { bg: 'var(--surface-inset)', fg: 'var(--text-muted)' },
    accent:  { bg: 'var(--accent-bg)', fg: 'var(--accent-text)' },
  };
  const c = tones[tone] || tones.neutral;
  return (
    <span style={{
      fontSize: 10.5, padding: '2px 7px', borderRadius: 999, fontWeight: 600,
      background: c.bg, color: c.fg, fontFamily: 'var(--font-mono)',
      letterSpacing: '.01em', whiteSpace: 'nowrap',
    }}>{children}</span>
  );
}

function ManageTemplateCard({ template, onEdit, onDuplicate, onDelete }) {
  const KebabMenu = window.KebabMenu;
  return (
    <article style={{
      background: 'var(--surface)', border: '1px solid var(--border)',
      borderRadius: 'var(--radius-md)', overflow: 'hidden',
      display: 'flex', flexDirection: 'column',
    }}>
      {/* Preview thumbnail — live iframe of the template's previewUrl */}
      <div style={{ height: 140, background: '#fff', borderBottom: '1px solid var(--border)',
        position: 'relative', overflow: 'hidden' }}>
        <iframe src={template.previewUrl} title={`Preview: ${template.name}`}
          tabIndex={-1} scrolling="no"
          style={{ width: '200%', height: '200%', border: 0, transform: 'scale(0.5)',
            transformOrigin: 'top left', background: '#fff', pointerEvents: 'none' }} />
        {template.isCustom && (
          <span style={{ position: 'absolute', top: 8, left: 8, fontSize: 10, fontWeight: 600,
            padding: '2px 7px', borderRadius: 999, background: 'var(--accent)', color: '#fff',
            fontFamily: 'var(--font-mono)' }}>custom</span>
        )}
      </div>

      <div style={{ padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 8, flex: 1 }}>
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 8 }}>
          <h3 style={{ margin: 0, fontSize: 13.5, fontWeight: 600, flex: 1, minWidth: 0,
            lineHeight: 1.3 }}>{template.name}</h3>
          <div style={{ marginTop: -4, marginRight: -6 }}>
            <KebabMenu items={[
              { label: 'Edit', icon: 'PenLine', onSelect: onEdit },
              { label: 'Duplicate', icon: 'Copy', onSelect: onDuplicate },
              { divider: true },
              { label: 'Delete', icon: 'Trash', danger: true, onSelect: onDelete },
            ]} />
          </div>
        </div>
        {template.description && (
          <p style={{ margin: 0, fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.5 }}>
            {template.description}
          </p>
        )}
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 5, marginTop: 'auto', paddingTop: 4 }}>
          <TemplateChip>{template.category}</TemplateChip>
          <TemplateChip tone="accent">{template.complexity}</TemplateChip>
        </div>
      </div>
    </article>
  );
}

// Edit / New form. HTML body is read-only for existing templates (View HTML
// toggle) — the full parameter-slot editor is Phase 2. New templates expose a
// paste-HTML textarea.
function TemplateEditModal({ template, onSave, onClose }) {
  const isNew = !!template._new;
  const [name, setName] = React.useState(template.name || '');
  const [description, setDescription] = React.useState(template.description || '');
  const [category, setCategory] = React.useState(template.category || '');
  const [complexity, setComplexity] = React.useState(template.complexity || 'med');
  const [html, setHtml] = React.useState(template._html || '');
  const [showHtml, setShowHtml] = React.useState(false);
  const [loadedHtml, setLoadedHtml] = React.useState(template._html || null);
  // Phase-2 tooltip slots — the {{name}} placeholders this template exposes.
  const [tooltipParams, setTooltipParams] = React.useState(() =>
    (template.tooltipParams || []).map(p => ({
      name: p.name || '',
      label: { ...(p.label || {}) },
      description: { ...(p.description || {}) },
    })));

  const addSlot = (seedName = '') => setTooltipParams(ps => [...ps,
    { name: seedName, label: { en: '' }, description: { en: '' } }]);
  const updSlot = (i, patch) => setTooltipParams(ps => ps.map((p, j) => j === i ? { ...p, ...patch } : p));
  const removeSlot = (i) => setTooltipParams(ps => ps.filter((_, j) => j !== i));

  // Validation — each name lowercase + underscores only, non-empty, unique.
  const nameValid = (n) => /^[a-z0-9_]+$/.test(n);
  const nameCounts = {};
  tooltipParams.forEach(p => { const k = p.name.trim(); if (k) nameCounts[k] = (nameCounts[k] || 0) + 1; });
  const slotError = (p) => {
    const n = p.name.trim();
    if (!n) return 'Name is required';
    if (!nameValid(n)) return 'Lowercase letters, numbers and underscores only';
    if (nameCounts[n] > 1) return 'Duplicate slot name';
    return null;
  };
  const slotsValid = tooltipParams.every(p => !slotError(p));

  // Detected {{…}} placeholders in pasted HTML (new-template flow) — surfaced as
  // hints, click to seed a slot row.
  const detected = isNew
    ? ((window.detectTooltipPlaceholders || (() => []))(html))
    : [];
  const definedNames = new Set(tooltipParams.map(p => p.name.trim()).filter(Boolean));

  // Read the HTML source from a hidden same-origin iframe (sandbox blocks
  // fetch() to the serve origin, but iframe contentDocument access is fine).
  const srcFrameRef = React.useRef(null);
  const readFromFrame = () => {
    try {
      const doc = srcFrameRef.current && srcFrameRef.current.contentDocument;
      if (doc) setLoadedHtml('<!DOCTYPE html>\n' + doc.documentElement.outerHTML);
    } catch (e) { setLoadedHtml('<!-- Could not read HTML source -->'); }
  };

  const canSave = name.trim().length > 0 && slotsValid;
  const COMPLEXITIES = ['low', 'low-med', 'med', 'med-high', 'high'];

  const labelStyle = { fontSize: 11.5, fontWeight: 600, color: 'var(--text-muted)',
    display: 'block', marginBottom: 6, letterSpacing: '.02em' };

  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'color-mix(in oklab, var(--text) 35%, transparent)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 50, padding: 24,
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        width: 600, maxWidth: '100%', maxHeight: '90vh', overflow: 'auto',
        background: 'var(--surface)', border: '1px solid var(--border)',
        borderRadius: 'var(--radius-md)', boxShadow: 'var(--shadow-lg)',
      }}>
        <div style={{ padding: '14px 18px', borderBottom: '1px solid var(--border)',
          display: 'flex', alignItems: 'center', gap: 10, position: 'sticky', top: 0,
          background: 'var(--surface)', zIndex: 1 }}>
          <div style={{ width: 26, height: 26, borderRadius: 6,
            background: 'var(--accent-bg)', color: 'var(--accent-text)',
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
            <I.FileText size={13} />
          </div>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 13, fontWeight: 600 }}>
              {isNew ? 'New template' : 'Edit template'}
            </div>
            <div style={{ fontSize: 11.5, color: 'var(--text-muted)' }}>
              {isNew ? 'Paste HTML and describe the scenario' : 'Update the library metadata for this template'}
            </div>
          </div>
          <button className="btn sm ghost" onClick={onClose}><I.X size={13} /></button>
        </div>

        <div style={{ padding: 18, display: 'flex', flexDirection: 'column', gap: 14 }}>
          <div>
            <label style={labelStyle}>Name</label>
            <input className="field" value={name} onChange={e => setName(e.target.value)}
              placeholder="e.g. Phishy email — typical" autoFocus />
          </div>
          <div>
            <label style={labelStyle}>Description</label>
            <textarea className="field" value={description} onChange={e => setDescription(e.target.value)}
              placeholder="One or two sentences shown on the template card…"
              style={{ minHeight: 64, fontSize: 12.5 }} />
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
            <div>
              <label style={labelStyle}>Category</label>
              <input className="field" value={category} onChange={e => setCategory(e.target.value)}
                placeholder="e.g. phishing-email" />
            </div>
            <div>
              <label style={labelStyle}>Complexity</label>
              <select className="field" value={complexity} onChange={e => setComplexity(e.target.value)}>
                {COMPLEXITIES.map(c => <option key={c} value={c}>{c}</option>)}
              </select>
            </div>
          </div>

          {isNew ? (
            <div>
              <label style={labelStyle}>Template HTML</label>
              <textarea className="field" value={html} onChange={e => setHtml(e.target.value)}
                placeholder={'Paste the template markup. Keep the <div class="quiz"> wrapper so the Player can mount it.'}
                style={{ minHeight: 130, fontSize: 11.5, fontFamily: 'var(--font-mono)' }} />
              <div style={{ fontSize: 11, color: 'var(--text-faint)', marginTop: 6 }}>
                Saved as <code style={{ fontFamily: 'var(--font-mono)' }}>uploads/html-samples/custom/{_slugify(name)}.html</code>
              </div>
            </div>
          ) : (
            <div>
              <button className="btn sm" onClick={() => setShowHtml(s => !s)}
                style={{ alignSelf: 'flex-start' }}>
                <I.FileText size={12} />{showHtml ? 'Hide HTML' : 'View HTML'}
              </button>
              {showHtml && (
                <>
                  {loadedHtml == null && !isNew && (
                    <iframe ref={srcFrameRef} src={template.previewUrl} title="HTML source loader"
                      onLoad={readFromFrame}
                      style={{ position: 'absolute', width: 1, height: 1, opacity: 0,
                        pointerEvents: 'none', border: 0 }} />
                  )}
                  <pre style={{
                    marginTop: 8, maxHeight: 240, overflow: 'auto',
                    background: 'var(--surface-inset)', border: '1px solid var(--border)',
                    borderRadius: 'var(--radius)', padding: 12, fontSize: 11,
                    fontFamily: 'var(--font-mono)', color: 'var(--text-muted)',
                    whiteSpace: 'pre-wrap', wordBreak: 'break-word',
                  }}>{loadedHtml == null ? 'Loading…' : loadedHtml}</pre>
                </>
              )}
              <div style={{ fontSize: 11, color: 'var(--text-faint)', marginTop: 6 }}>
                HTML is read-only for the starter templates.
              </div>
            </div>
          )}

          {/* ── Tooltip slots (Phase 2) ─────────────────────────────────── */}
          <div style={{ borderTop: '1px solid var(--border)', paddingTop: 14,
            display: 'flex', flexDirection: 'column', gap: 10 }}>
            <div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
                <I.MessageSquare size={13} style={{ color: 'var(--text-muted)' }} />
                <span style={{ fontSize: 12.5, fontWeight: 600 }}>Tooltip slots</span>
                <span style={{ fontSize: 11, color: 'var(--text-faint)' }}>
                  {tooltipParams.length} slot{tooltipParams.length === 1 ? '' : 's'}
                </span>
              </div>
              <p style={{ margin: '5px 0 0', fontSize: 11.5, color: 'var(--text-muted)', lineHeight: 1.5 }}>
                Define the tooltip text slots this template exposes. Authors fill in the actual
                tooltip text per course; you define the slot names and labels.
              </p>
            </div>

            {/* Detected placeholders in pasted HTML (new template) */}
            {isNew && detected.length > 0 && (
              <div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 6,
                fontSize: 11.5, color: 'var(--text-muted)' }}>
                <span>Detected in HTML:</span>
                {detected.map(n => {
                  const already = definedNames.has(n);
                  return (
                    <button key={n} onClick={() => !already && addSlot(n)} disabled={already}
                      title={already ? 'Already defined as a slot' : `Add slot "${n}"`}
                      style={{ display: 'inline-flex', alignItems: 'center', gap: 4,
                        padding: '2px 8px', borderRadius: 999, border: '1px solid var(--border)',
                        background: already ? 'var(--surface-inset)' : 'var(--surface)',
                        color: already ? 'var(--text-faint)' : 'var(--accent-text)',
                        fontFamily: 'var(--font-mono)', fontSize: 10.5, cursor: 'default',
                        fontWeight: 500 }}>
                      {already ? <I.Check size={10} /> : <I.Plus size={10} />}{`{{${n}}}`}
                    </button>
                  );
                })}
              </div>
            )}

            {tooltipParams.length === 0 ? (
              <div style={{ padding: '12px 14px', background: 'var(--surface-inset)',
                border: '1px dashed var(--border)', borderRadius: 'var(--radius)',
                fontSize: 11.5, color: 'var(--text-muted)', textAlign: 'center' }}>
                No tooltip slots yet. Add one for each highlighted term the template should explain.
              </div>
            ) : (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                {tooltipParams.map((p, i) => {
                  const err = slotError(p);
                  return (
                    <div key={i} style={{ border: '1px solid var(--border)',
                      borderRadius: 'var(--radius-md)', background: 'var(--surface)', padding: 12,
                      display: 'flex', flexDirection: 'column', gap: 8 }}>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                        <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)',
                          color: 'var(--text-faint)' }}>{'{{ }}'}</span>
                        <input className="field" value={p.name}
                          onChange={e => updSlot(i, { name: e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '') })}
                          placeholder="slot_name"
                          style={{ flex: 1, fontFamily: 'var(--font-mono)', fontSize: 12,
                            borderColor: err ? 'var(--error-text)' : undefined }} />
                        <button className="btn sm ghost" onClick={() => removeSlot(i)}
                          title="Remove slot"
                          style={{ width: 28, height: 28, padding: 0, justifyContent: 'center',
                            color: 'var(--text-faint)' }}>
                          <I.Trash size={13} />
                        </button>
                      </div>
                      {err && (
                        <div style={{ fontSize: 11, color: 'var(--error-text)',
                          display: 'inline-flex', alignItems: 'center', gap: 5 }}>
                          <I.AlertCircle size={11} />{err}
                        </div>
                      )}
                      <div>
                        <label style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)',
                          display: 'block', marginBottom: 4 }}>Label (what Authors see)</label>
                        <ControlledLocalized value={p.label}
                          onChange={v => updSlot(i, { label: v })} />
                      </div>
                      <div>
                        <label style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)',
                          display: 'block', marginBottom: 4 }}>Description (helper text)</label>
                        <ControlledLocalized value={p.description} multiline
                          onChange={v => updSlot(i, { description: v })} />
                      </div>
                    </div>
                  );
                })}
              </div>
            )}

            <button className="btn sm" onClick={() => addSlot()}
              style={{ alignSelf: 'flex-start', borderStyle: 'dashed' }}>
              <I.Plus size={12} />Add tooltip slot
            </button>
          </div>

          <div style={{ display: 'flex', gap: 8, marginTop: 2 }}>
            <div style={{ flex: 1 }} />
            <button className="btn sm ghost" onClick={onClose}>Cancel</button>
            <button className="btn sm primary" disabled={!canSave}
              style={{ opacity: canSave ? 1 : 0.5 }}
              onClick={() => canSave && onSave({
                ...template, name: name.trim(), description, category, complexity, _html: html,
                tooltipParams: tooltipParams
                  .filter(p => p.name.trim())
                  .map(p => ({ name: p.name.trim(), label: p.label || { en: '' },
                    description: p.description || { en: '' } })),
              })}>
              <I.Check size={12} />{isNew ? 'Create template' : 'Save changes'}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { ManageTemplatesScreen });
