// Surface 3 — Layout editor (the workhorse).
// Left column (~70%): per-layout edit form
// Right column (~30%): Section context, anecdotes, other suggestions

function SurfaceLayoutEditor({ course, selectedLayoutId, onChangeLayoutType, onBack,
  onChangeLayoutStatus, onSelectLayout, draft: externalDraft, onChangeDraft, allDrafts,
  gamingQuizEnabled = false, dftiFlow, onChangeDftiFlow }) {
  // Find the selected layout across modules
  let module = null, layout = null;
  course.modules.forEach(m => {
    m.layouts.forEach(l => { if (l.id === selectedLayoutId) { module = m; layout = l; } });
  });
  if (!layout) { module = course.modules[1]; layout = module.layouts[2]; }

  // `layoutType` is a DISPLAY id (drives the dropdown). For hidden_items it is
  // a flat/360° display id; the value written to the data is the CANONICAL type
  // (canonicalLayoutType) — hidden_items is UI sugar (Fix 4). Initialise from
  // the existing draft if present (so a 360° layout re-opens on the 360° id),
  // else from the architecture entry.
  const initialDisplayType = window.displayLayoutType(externalDraft && externalDraft.type
    ? externalDraft : layout) || layout.type;
  const [layoutType, setLayoutType] = React.useState(initialDisplayType);
  const canon = window.canonicalLayoutType(layoutType);
  const status = layout.status;
  const setStatus = React.useCallback((next) => {
    if (onChangeLayoutStatus) onChangeLayoutStatus(layout.id, next);
  }, [onChangeLayoutStatus, layout.id]);

  // Draft: the editable layout data. Seeded from sample-content + inline layout
  // fields; persists at the app level so the Module preview and PreviewPane
  // mirror the same edits. `type` is canonicalised so the stored data never
  // carries a hidden_items display id.
  const seed = React.useMemo(() => ({
    ...(window.LAYOUT_CONTENT_SAMPLES?.[layoutType] || {}),
    ...(layout || {}),
    type: window.canonicalLayoutType(layoutType),
  }), [layout, layoutType]);
  // IMPORTANT: render against a draft whose `.type` matches the current
  // `layoutType` (canonically). If the user just swapped types via the
  // dropdown, the app-level draft still holds the *previous* type's shape until
  // the effect below pushes the new seed. Falling back to `seed` for that one
  // render ensures uncontrolled children (e.g. BackgroundPicker's mode/color
  // state) never mount against a stale shape.
  const draft = (externalDraft && externalDraft.type === canon)
    ? externalDraft
    : seed;
  // Track the display id we last seeded for, so a flat↔360° switch (same
  // canonical type) still re-seeds with the right media field.
  const seededFor = React.useRef(null);
  React.useEffect(() => {
    // Persist the seed up to the app whenever it diverges from the stored
    // draft — on entering the editor, on a canonical type change, OR on a
    // flat↔360° display switch that keeps the canonical type the same.
    const displayChanged = seededFor.current != null && seededFor.current !== layoutType;
    if (!externalDraft || externalDraft.type !== canon || displayChanged) {
      onChangeDraft?.(seed);
    }
    seededFor.current = layoutType;
  }, [layoutType, externalDraft, seed, canon, onChangeDraft]);

  // Keep a ref to the latest draft so multiple setPath / setField calls
  // inside the same event handler ACCUMULATE instead of clobbering each
  // other. Without this, e.g. switching a row from Image → Video (which
  // writes videoUrl + videoThumbUrl + clears imageUrl in sequence) would
  // silently drop all but the last write, since each closure read the
  // pre-update draft and the parent re-render hadn't happened yet.
  const draftRef = React.useRef(draft);
  draftRef.current = draft;
  const commit = React.useCallback((next) => {
    draftRef.current = next;
    onChangeDraft?.(next);
  }, [onChangeDraft]);
  const setField = React.useCallback((k, v) => {
    commit({ ...draftRef.current, [k]: v });
  }, [commit]);
  const setPath = React.useCallback((path, v) => {
    commit(setAtPath(draftRef.current, path, v));
  }, [commit]);
  const ctx = window.SAMPLE_SECTION_CONTEXT;

  return (
    <div style={{
      display: 'grid',
      gridTemplateColumns: 'minmax(0, 1fr) 440px',
      gridTemplateRows: 'minmax(0, 1fr)',
      height: '100%',
      background: 'var(--bg)',
      overflow: 'hidden',
    }} data-screen-label="Surface 3 · Layout editor">

      {/* ── Left column · Editor ─────────────────────────────────── */}
      <section style={{ display: 'flex', flexDirection: 'column', minHeight: 0 }}>
        <LayoutEditorHeader
          module={module}
          layout={layout}
          layoutType={layoutType}
          status={status}
          onChangeStatus={setStatus}
          onChangeType={setLayoutType}
          course={course}
          gamingQuizEnabled={gamingQuizEnabled}
          onBack={onBack} />

        <div style={{ flex: 1, overflowY: 'auto', padding: '20px 28px 32px',
          display: 'flex', flexDirection: 'column', gap: 20 }}>

          {/* Gaming quiz disabled at course level — see Course settings.
              The layout still exists in the architecture; this ribbon warns the
              author it can't be edited/exported until the master switch is on. */}
          {layoutType === 'quiz_gaming' && !gamingQuizEnabled && (
            <GamingQuizDisabledRibbon />
          )}

          {/* The per-type editor handles its own controls (Title/Subtitle
              presence varies per the catalogue). `courseValue` / `onCourseChange`
              expose the shared course-level slice — only quiz_gaming's Profile
              form tab consumes them; every other editor ignores them. */}
          <PerTypeEditor type={layoutType} draft={draft}
            setField={setField} setPath={setPath}
            courseValue={dftiFlow} onCourseChange={onChangeDftiFlow} />
        </div>
      </section>

      {/* ── Right column · Module preview ────────────────────────── */}
      <LayoutEditorRightPanel context={ctx} layout={draft} layoutType={layoutType}
        module={module} onSelectLayout={onSelectLayout} allDrafts={allDrafts} />
    </div>
  );
}

// ── Gaming-quiz-disabled ribbon — shown on an existing quiz_gaming layout when
// ── the course-level master switch (Course settings) is off.
function GamingQuizDisabledRibbon() {
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 10,
      padding: '10px 14px', background: 'var(--warning-bg)',
      border: '1px solid var(--warning)', borderRadius: 'var(--radius-md)',
      color: 'var(--warning-text)',
    }}>
      <I.AlertTriangle size={15} style={{ flexShrink: 0 }} />
      <div style={{ fontSize: 12.5, lineHeight: 1.45 }}>
        <strong style={{ fontWeight: 600 }}>Gaming quiz disabled at course level.</strong>{' '}
        Re-enable the gaming quiz flow in Course settings to edit this layout.
      </div>
    </div>
  );
}

// ── Header for the layout editor ────────────────────────────────────────────
function LayoutEditorHeader({ module, layout, layoutType, status, onChangeStatus, onChangeType, course, gamingQuizEnabled, onBack }) {
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 12,
      padding: '12px 24px',
      borderBottom: '1px solid var(--border)',
      background: 'var(--surface)',
      minHeight: 56,
    }}>
      <button className="btn sm ghost" onClick={onBack}><I.ArrowLeft size={13} />Back to architecture</button>
      <div style={{ width: 1, height: 20, background: 'var(--border)' }} />

      <span style={{ fontSize: 12, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>
        {module.id}.L{(module.layouts.findIndex(l => l.id === layout.id) + 1) || layout.n || '?'}
      </span>

      <LayoutTypeSwitcher current={layoutType} onChange={onChangeType}
        gamingQuizEnabled={gamingQuizEnabled} />

      <StatusPill status={status} />

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

      <LayoutHeaderActions status={status} onChangeStatus={onChangeStatus}
        layout={layout} module={module} course={course} />
    </div>
  );
}

// ── LayoutHeaderActions — status-aware primary + secondary CTAs
function LayoutHeaderActions({ status, onChangeStatus, layout, module, course }) {
  const [confirm, setConfirm] = React.useState(null); // 'reroll' | 'delete' | 'duplicate' | null

  const baseMenu = [
    { label: 'Duplicate layout', icon: 'Copy',
      hint: 'Copy this layout into another module',
      onSelect: () => setConfirm('duplicate') },
    { divider: true },
    { label: 'Delete layout', icon: 'Trash', danger: true,
      onSelect: () => setConfirm('delete') },
  ];
  // In Pending state, the "hand-author" path lives in the kebab so the
  // header isn't competing for primary-CTA real-estate with two equally-
  // weighted buttons. Generate stays as the single visible primary.
  const menuItems = status === 'pending'
    ? [
        { label: 'Hand-author content', icon: 'PenLine',
          hint: 'Type the fields instead of generating',
          onSelect: () => onChangeStatus('proposed') },
        { divider: true },
        ...baseMenu,
      ]
    : baseMenu;
  const overflow = <KebabMenu items={menuItems} />;

  // Secondary action (Re-roll variant) — only shown when there's something
  // to re-roll. In Pending there's no content yet, so the slot is empty.
  let secondary = null;
  if (status !== 'pending') {
    secondary = (
      <button className="btn sm"
        onClick={() => status === 'accepted' ? setConfirm('reroll') : onChangeStatus('proposed')}
        title={status === 'accepted'
          ? 'Re-rolling will replace this accepted content'
          : 'Ask AI for a different draft of this layout'}>
        <I.RefreshCw size={13} />Re-roll
      </button>
    );
  }

  let primary = null;
  if (status === 'proposed') {
    primary = (
      <button className="btn sm primary" onClick={() => onChangeStatus('accepted')}>
        <I.Check size={13} />Accept
      </button>
    );
  } else if (status === 'accepted') {
    primary = (
      <button className="btn sm" onClick={() => onChangeStatus('proposed')}
        title="Click for edits to unlock">
        <I.Unlock size={13} />Re-open for edits
      </button>
    );
  } else if (status === 'issues') {
    primary = (
      <button className="btn sm" style={{
        background: 'var(--warning-bg)', color: 'var(--warning-text)',
        border: '1px solid var(--warning)',
      }}>
        <I.AlertTriangle size={13} />Fix 2 issues
      </button>
    );
  } else if (status === 'pending') {
    primary = (
      <button className="btn sm ai" onClick={() => onChangeStatus('proposed')}
        title="Use AI to generate content from the source paragraphs">
        <I.Sparkle size={13} />Generate
      </button>
    );
  }

  return (
    <>
      {overflow}
      {secondary}
      {primary}
      {confirm === 'reroll' && (
        <ConfirmDialog
          icon="RefreshCw"
          title="Re-roll this accepted layout?"
          body="This will discard the accepted content and ask AI for a new draft. The layout will go back to Proposed state until you accept again."
          confirmLabel="Re-roll and replace"
          tone="warning"
          onCancel={() => setConfirm(null)}
          onConfirm={() => { setConfirm(null); onChangeStatus('proposed'); }} />
      )}
      {confirm === 'delete' && (
        <ConfirmDialog
          icon="Trash"
          title={`Delete layout ${layout.id}?`}
          body="This removes the layout from the module. The source paragraphs it covered will be marked unmapped and surface in Validation."
          confirmLabel="Delete layout"
          tone="danger"
          onCancel={() => setConfirm(null)}
          onConfirm={() => setConfirm(null)} />
      )}
      {confirm === 'duplicate' && (
        <DuplicateLayoutDialog
          layout={layout}
          sourceModule={module}
          modules={course?.modules || []}
          onCancel={() => setConfirm(null)}
          onConfirm={() => setConfirm(null)} />
      )}
    </>
  );
}

// ── DuplicateLayoutDialog ─ pick which module the duplicate lands in
function DuplicateLayoutDialog({ layout, sourceModule, modules, onCancel, onConfirm }) {
  const [targetId, setTargetId] = React.useState(sourceModule?.id || modules[0]?.id);
  const target = modules.find(m => m.id === targetId) || modules[0];
  const meta = (window.LAYOUT_TYPES || []).find(t => t.id === layout.type) || {};
  const Icon = I[meta.icon] || I.Hash;

  return (
    <>
      <div onClick={onCancel} style={{
        position: 'fixed', inset: 0, background: 'rgba(15,23,42,0.4)',
        backdropFilter: 'blur(2px)', zIndex: 60,
      }} />
      <div style={{
        position: 'fixed', top: '22%', left: '50%', transform: 'translateX(-50%)',
        width: 'min(520px, 92vw)', zIndex: 61,
        background: 'var(--surface)', border: '1px solid var(--border)',
        borderRadius: 'var(--radius-lg)', boxShadow: 'var(--shadow-xl)',
        padding: 20,
      }}>
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, marginBottom: 16 }}>
          <div style={{
            width: 32, height: 32, borderRadius: '50%', background: 'var(--accent-bg)',
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
            color: 'var(--accent-text)', flexShrink: 0,
          }}>
            <I.Copy size={16} />
          </div>
          <div style={{ flex: 1 }}>
            <h3 style={{ margin: 0, fontSize: 14, fontWeight: 600 }}>
              Duplicate layout {layout.id}
            </h3>
          </div>
        </div>

        {/* Layout summary */}
        <div style={{
          display: 'flex', alignItems: 'center', gap: 10,
          padding: '10px 12px', background: 'var(--surface-inset)',
          borderRadius: 'var(--radius-md)', marginBottom: 14,
        }}>
          <Icon size={14} style={{ color: 'var(--text-muted)' }} />
          <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'var(--text-muted)' }}>
            {layout.type}
          </span>
          <span style={{ width: 1, height: 12, background: 'var(--border)' }} />
          <span className="truncate" style={{ fontSize: 12.5, color: 'var(--text)', flex: 1 }}>
            {locText(layout.summary)}
          </span>
        </div>

        {/* Destination module */}
        <div style={{ marginBottom: 18 }}>
          <label style={{ display: 'block', fontSize: 11, color: 'var(--text-muted)',
            fontWeight: 600, letterSpacing: '.04em', textTransform: 'uppercase', marginBottom: 6 }}>
            Destination module
          </label>
          <div style={{ display: 'grid', gap: 4, maxHeight: 200, overflowY: 'auto',
            border: '1px solid var(--border)', borderRadius: 'var(--radius-md)', padding: 4 }}>
            {modules.map(m => {
              const sel = m.id === targetId;
              const isSource = m.id === sourceModule?.id;
              return (
                <button key={m.id} onClick={() => setTargetId(m.id)}
                  style={{
                    display: 'grid', gridTemplateColumns: 'auto auto 1fr auto', gap: 10,
                    alignItems: 'center', padding: '8px 10px', textAlign: 'left',
                    background: sel ? 'var(--accent-bg)' : 'transparent',
                    border: '1px solid', borderColor: sel ? 'var(--accent-border)' : 'transparent',
                    borderRadius: 'var(--radius)', cursor: 'default', fontFamily: 'inherit',
                    color: 'var(--text)',
                  }}
                  onMouseOver={e => { if (!sel) e.currentTarget.style.background = 'var(--surface-inset)'; }}
                  onMouseOut={e => { if (!sel) e.currentTarget.style.background = 'transparent'; }}>
                  <span style={{
                    width: 14, height: 14, borderRadius: '50%',
                    border: '1.5px solid', borderColor: sel ? 'var(--accent)' : 'var(--border-strong)',
                    background: sel ? 'var(--accent)' : 'transparent',
                    display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                  }}>{sel && <I.Check size={8} style={{ color: '#fff' }} />}</span>
                  <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 600,
                    color: 'var(--text-muted)', letterSpacing: '.04em' }}>{m.id}</span>
                  <span className="truncate" style={{ fontSize: 12.5 }}>{locText(m.title)}</span>
                  <span style={{ fontSize: 11, color: 'var(--text-faint)',
                    fontFamily: 'var(--font-mono)' }}>
                    {(m.layouts?.length || 0)} layouts
                    {isSource && <span style={{ marginLeft: 6,
                      padding: '1px 5px', background: 'var(--surface-inset)',
                      color: 'var(--text-muted)', borderRadius: 3,
                      fontFamily: 'inherit', textTransform: 'uppercase', letterSpacing: '.04em',
                      fontSize: 9.5, fontWeight: 600 }}>Current</span>}
                  </span>
                </button>
              );
            })}
          </div>
        </div>

        <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
          <button className="btn sm ghost" onClick={onCancel}>Cancel</button>
          <button className="btn sm primary" onClick={onConfirm}>
            <I.Copy size={13} />Duplicate into {target?.id}
          </button>
        </div>
      </div>
    </>
  );
}

// ── KebabMenu — generic overflow menu (used by the header)
function KebabMenu({ items = [] }) {
  const [open, setOpen] = React.useState(false);
  return (
    <div style={{ position: 'relative' }}>
      <button className="btn sm ghost" onClick={() => setOpen(o => !o)}
        title="More"><I.MoreHorizontal size={13} /></button>
      {open && (
        <>
          <div onClick={() => setOpen(false)}
            style={{ position: 'fixed', inset: 0, zIndex: 30 }} />
          <div style={{
            position: 'absolute', top: 'calc(100% + 4px)', right: 0, zIndex: 31,
            minWidth: 240, padding: 6,
            background: 'var(--surface)', border: '1px solid var(--border)',
            borderRadius: 'var(--radius-md)', boxShadow: 'var(--shadow-lg)',
          }}>
            {items.map((it, i) => it.divider ? (
              <div key={i} style={{ height: 1, background: 'var(--border)', margin: '4px -2px' }} />
            ) : (
              <button key={i} onClick={() => { it.onSelect?.(); setOpen(false); }}
                style={{
                  display: 'flex', alignItems: 'flex-start', gap: 8,
                  width: '100%', padding: '7px 9px', textAlign: 'left',
                  background: 'transparent', border: 0, borderRadius: 'var(--radius)',
                  cursor: 'default', fontFamily: 'inherit',
                  color: it.danger ? 'var(--error-text)' : 'var(--text)',
                  fontSize: 12.5,
                }}
                onMouseOver={e => e.currentTarget.style.background = 'var(--surface-inset)'}
                onMouseOut={e => e.currentTarget.style.background = 'transparent'}>
                {it.icon && React.createElement(I[it.icon] || I.Hash, {
                  size: 13, style: { marginTop: 2,
                    color: it.danger ? 'var(--error-text)' : 'var(--text-muted)' } })}
                <div style={{ flex: 1 }}>
                  <div>{it.label}</div>
                  {it.hint && <div style={{ fontSize: 11, color: 'var(--text-faint)', marginTop: 1 }}>
                    {it.hint}</div>}
                </div>
              </button>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

// ── ConfirmDialog — small modal for destructive actions
function ConfirmDialog({ icon = 'AlertCircle', title, body, confirmLabel = 'Confirm',
  cancelLabel = 'Cancel', tone = 'danger', onConfirm, onCancel }) {
  const Icon = I[icon] || I.AlertCircle;
  const tint = tone === 'danger' ? 'var(--error-text)'
             : tone === 'warning' ? 'var(--warning-text)'
             : 'var(--accent-text)';
  const bg = tone === 'danger' ? 'var(--error-bg)'
           : tone === 'warning' ? 'var(--warning-bg)'
           : 'var(--accent-bg)';
  return (
    <>
      <div onClick={onCancel} style={{
        position: 'fixed', inset: 0, background: 'rgba(15,23,42,0.4)',
        backdropFilter: 'blur(2px)', zIndex: 60,
      }} />
      <div style={{
        position: 'fixed', top: '30%', left: '50%', transform: 'translateX(-50%)',
        width: 'min(440px, 92vw)', zIndex: 61,
        background: 'var(--surface)', border: '1px solid var(--border)',
        borderRadius: 'var(--radius-lg)', boxShadow: 'var(--shadow-xl)',
        padding: 18,
      }}>
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, marginBottom: 12 }}>
          <div style={{
            width: 32, height: 32, borderRadius: '50%', background: bg,
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
            color: tint, flexShrink: 0,
          }}>
            <Icon size={16} />
          </div>
          <div style={{ flex: 1 }}>
            <h3 style={{ margin: 0, fontSize: 14, fontWeight: 600 }}>{title}</h3>
            <p style={{ margin: '6px 0 0', fontSize: 12.5, color: 'var(--text-muted)',
              lineHeight: 1.5 }}>{body}</p>
          </div>
        </div>
        <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
          <button className="btn sm ghost" onClick={onCancel}>{cancelLabel}</button>
          <button className={`btn sm ${tone === 'danger' ? 'danger' : 'primary'}`}
            style={tone === 'warning' ? { background: 'var(--warning)', color: '#fff',
              borderColor: 'var(--warning)' } : undefined}
            onClick={onConfirm}>{confirmLabel}</button>
        </div>
      </div>
    </>
  );
}

// ── LayoutTypeSwitcher ──────────────────────────────────────────────────────
function LayoutTypeSwitcher({ current, onChange, gamingQuizEnabled = true }) {
  const [open, setOpen] = React.useState(false);
  const meta = (window.LAYOUT_TYPES || []).find(t => t.id === current) || {};
  const Icon = I[meta.icon] || I.Hash;
  const groups = {};
  (window.LAYOUT_TYPES || []).filter(t => !t.hidden)
    .forEach(t => { (groups[t.group] = groups[t.group] || []).push(t); });
  const visibleCount = (window.LAYOUT_TYPES || []).filter(t => !t.hidden).length;

  return (
    <div style={{ position: 'relative' }}>
      <button className="btn sm" onClick={() => setOpen(o => !o)}
        style={{ height: 28, paddingRight: 8 }}>
        <Icon size={13} />
        <span style={{ fontFamily: 'var(--font-mono)' }}>{current}</span>
        <I.ChevronDown size={12} style={{ opacity: 0.6 }} />
      </button>
      {open && (
        <>
          <div onClick={() => setOpen(false)}
            style={{ position: 'fixed', inset: 0, zIndex: 30 }} />
          <div style={{
            position: 'absolute', top: 'calc(100% + 4px)', left: 0,
            background: 'var(--surface)', border: '1px solid var(--border)',
            borderRadius: 'var(--radius-md)', boxShadow: 'var(--shadow-lg)',
            width: 320, padding: 8, zIndex: 31, maxHeight: 480, overflowY: 'auto',
          }}>
            <div style={{ padding: '8px 8px 4px', fontSize: 11, color: 'var(--text-faint)',
              fontWeight: 600, letterSpacing: '.04em', textTransform: 'uppercase' }}>
              Switch layout type
              <span style={{ fontWeight: 400, textTransform: 'none', marginLeft: 6, color: 'var(--text-faint)' }}>
                · {visibleCount} types
              </span>
            </div>
            {Object.entries(groups).map(([g, items]) => (
              <div key={g}>
                <div style={{ padding: '8px 8px 2px', fontSize: 10.5, color: 'var(--text-faint)',
                  fontWeight: 600, letterSpacing: '.06em', textTransform: 'uppercase' }}>{g}</div>
                {items.map(t => {
                  const Ti = I[t.icon] || I.Hash;
                  const active = current === t.id;
                  // Gaming quiz is gated by the course-level master switch
                  // (Course settings · Gaming quiz flow). When off, it stays
                  // visible but un-pickable with an explanatory tooltip — unless
                  // this layout is already that type (so the author isn't locked
                  // out of the row they're on).
                  const lockedGaming = t.id === 'quiz_gaming' && !gamingQuizEnabled && !active;
                  return (
                    <button key={t.id} disabled={lockedGaming}
                      title={lockedGaming
                        ? 'Enable the gaming quiz flow in Course settings to use this layout type.'
                        : undefined}
                      onClick={() => { if (lockedGaming) return; onChange(t.id); setOpen(false); }}
                      style={{
                        display: 'flex', alignItems: 'center', gap: 8,
                        width: '100%', padding: '6px 8px', textAlign: 'left',
                        border: 0, borderRadius: 'var(--radius)',
                        background: active ? 'var(--accent-bg)' : 'transparent',
                        color: lockedGaming ? 'var(--text-faint)'
                          : active ? 'var(--accent-text)' : 'var(--text)',
                        cursor: 'default', fontSize: 12.5, fontFamily: 'inherit',
                        opacity: lockedGaming ? 0.6 : 1,
                      }}
                      onMouseOver={e => { if (!active && !lockedGaming) e.currentTarget.style.background = 'var(--surface-inset)'; }}
                      onMouseOut={e => { if (!active) e.currentTarget.style.background = 'transparent'; }}>
                      <Ti size={14} />
                      <span style={{ fontFamily: 'var(--font-mono)', flex: 1 }}>{t.id}</span>
                      {active && <I.Check size={13} />}
                      {lockedGaming && <I.Lock size={12} style={{ color: 'var(--text-faint)' }} />}
                    </button>
                  );
                })}
              </div>
            ))}
            <div style={{
              margin: '8px -8px -8px', padding: '8px 12px',
              borderTop: '1px solid var(--border)', background: 'var(--surface-2)',
              borderRadius: '0 0 var(--radius-md) var(--radius-md)',
              fontSize: 11.5, color: 'var(--text-muted)',
            }}>
              <I.Info size={11} /> Switching may lose fields that don't migrate
            </div>
          </div>
        </>
      )}
    </div>
  );
}

// ── EditorBlock — section header inside the editor ─────────────────────────
function EditorBlock({ label, count, action, children }) {
  return (
    <section>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
        <h3 style={{ margin: 0, fontSize: 13, fontWeight: 600, color: 'var(--text)',
          letterSpacing: '-.005em', whiteSpace: 'nowrap', flexShrink: 0 }}>{label}</h3>
        {count != null && <span style={{
          fontSize: 11, color: 'var(--text-faint)',
          fontFamily: 'var(--font-mono)',
          background: 'var(--surface-inset)', padding: '1px 5px', borderRadius: 3,
        }}>{count}</span>}
        <div style={{ flex: 1, height: 1, background: 'var(--border)', marginLeft: 4 }} />
        {action}
      </div>
      {children}
    </section>
  );
}

// ── SequenceEditor — the populated sequence (M2-L3) ────────────────────────
function SequenceEditor() {
  const tabs = window.SAMPLE_SEQUENCE_TABS;
  const [activeTab, setActiveTab] = React.useState(tabs[0].id);
  const active = tabs.find(t => t.id === activeTab);

  return (
    <>
      {/* Tab preview strip */}
      <EditorBlock label="Tab progression preview"
        action={<button className="btn sm ghost"><I.Eye size={12} />Preview in player</button>}>
        <div style={{
          padding: '14px 16px', background: 'var(--surface)',
          border: '1px solid var(--border)', borderRadius: 'var(--radius-md)',
          display: 'flex', flexDirection: 'column', gap: 12, alignItems: 'center',
        }}>
          <div style={{ display: 'flex', gap: 10 }}>
            {tabs.map((t, i) => (
              <button key={t.id} onClick={() => setActiveTab(t.id)}
                style={{
                  width: 22, height: 22, borderRadius: '50%',
                  border: '1.5px solid', borderColor: t.id === activeTab ? 'var(--accent)' : 'var(--border-strong)',
                  background: t.id === activeTab ? 'var(--accent)' : 'transparent',
                  color: t.id === activeTab ? '#fff' : 'var(--text-muted)',
                  fontSize: 10.5, fontWeight: 600, cursor: 'default', fontFamily: 'inherit',
                  display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                  position: 'relative',
                }}>
                {i + 1}
                {t.branch === 'correct' && (
                  <span style={{ position: 'absolute', top: -4, right: -4,
                    width: 10, height: 10, background: 'var(--success)', color: '#fff',
                    borderRadius: '50%', fontSize: 7, lineHeight: '10px',
                    display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700 }}>
                    ✓
                  </span>
                )}
              </button>
            ))}
          </div>
          <span style={{ fontSize: 11.5, color: 'var(--text-faint)' }}>
            Click a dot to edit that tab · 6 tabs · learner sees this strip in the Player footer
          </span>
        </div>
      </EditorBlock>

      {/* Tab list */}
      <EditorBlock label="Tabs" count={tabs.length}
        action={<button className="btn sm"><I.Plus size={12} />Add tab</button>}>
        <div style={{ display: 'grid', gap: 6 }}>
          {tabs.map((t, i) => (
            <SequenceTabRow key={t.id} tab={t} index={i}
              active={t.id === activeTab}
              onClick={() => setActiveTab(t.id)} />
          ))}
        </div>
      </EditorBlock>

      {/* Active tab editor */}
      <EditorBlock label={`Editing tab ${tabs.findIndex(t => t.id === activeTab) + 1}: ${active.title}`}>
        <SequenceTabEditor tab={active} />
      </EditorBlock>
    </>
  );
}

function SequenceTabRow({ tab, index, active, onClick }) {
  const kindMeta = {
    text:     { label: 'Text',     icon: I.FileText, tint: 'var(--text-muted)' },
    video:    { label: 'Video',    icon: I.Film,     tint: 'var(--accent)' },
    questionMultiple: { label: 'Question', icon: I.AlertCircle, tint: 'var(--warning)' },
    questionShort: { label: 'Short Q', icon: I.AlertCircle, tint: 'var(--warning)' },
    question: { label: 'Question', icon: I.AlertCircle, tint: 'var(--warning)' },
  }[tab.kind];
  const KI = kindMeta?.icon || I.Hash;
  return (
    <div onClick={onClick}
      style={{
        display: 'grid', gridTemplateColumns: 'auto 28px 100px 1fr auto auto',
        gap: 10, alignItems: 'center',
        padding: '8px 10px',
        background: active ? 'var(--accent-bg)' : 'var(--surface)',
        border: '1px solid', borderColor: active ? 'var(--accent-border)' : 'var(--border)',
        borderRadius: 'var(--radius)', cursor: 'default',
      }}
      onMouseOver={e => { if (!active) e.currentTarget.style.borderColor = 'var(--border-strong)'; }}
      onMouseOut={e => { if (!active) e.currentTarget.style.borderColor = 'var(--border)'; }}>
      <I.GripVertical size={13} style={{ color: 'var(--text-faint)' }} />
      <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--text-muted)' }}>
        T{index + 1}
      </span>
      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5,
        fontSize: 11.5, color: kindMeta?.tint || 'var(--text-muted)' }}>
        <KI size={12} />{kindMeta?.label || tab.kind}
      </span>
      <span className="truncate" style={{ fontSize: 12.5, color: 'var(--text)' }}>
        {tab.title}
      </span>
      <span style={{ fontSize: 11, color: 'var(--text-faint)' }}>
        {tab.mobileText}
      </span>
      <button className="btn sm ghost"><I.MoreHorizontal size={12} /></button>
    </div>
  );
}

function SequenceTabEditor({ tab }) {
  return (
    <div style={{ display: 'grid', gap: 14 }}>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 200px', gap: 12 }}>
        <input key={tab.id + ':title'} className="field" defaultValue={tab.title}
          style={{ height: 36, fontSize: 14, fontWeight: 600 }} />
        <input key={tab.id + ':mob'} className="field" defaultValue={tab.mobileText} placeholder="Mobile label"
          style={{ height: 36 }} />
      </div>

      {tab.kind === 'text' && (
        <>
          <RichTextEditor key={tab.id + ':body'} value={tab.body}
            placeholder="Tab body text — bold, italic, lists, inline links supported." />
          <MediaSlot kind="image" label="Background image (optional)"
            bound={{ alt: 'Scenario opener · office', previewBg: 'linear-gradient(135deg, #64748b, #1e293b)', generatedBy: 'Gemini' }} />
        </>
      )}

      {tab.kind === 'video' && (
        <>
          <MediaSlot kind="video"
            bound={{ alt: 'Sarah prep · how to use SBI (00:00 - 01:24)', duration: '01:24',
              previewBg: 'linear-gradient(135deg, #475569, #0f172a)', generatedBy: 'HeyGen' }} />
          <RichTextEditor key={tab.id + ':cap'} value={tab.body} placeholder="Caption text under video…" />
        </>
      )}

      {(tab.kind === 'questionMultiple' || tab.kind === 'question' || tab.kind === 'questionShort') && (
        <>
          <RichTextEditor key={tab.id + ':prompt'} value={tab.body} placeholder="Question prompt…" minHeight={64} />
          <AnswersEditor answers={[
            { text: 'When you missed the API spec review on Tuesday and the design sign-off on Friday, two engineers were blocked.',
              isCorrect: true, feedback: 'Right — specific, observable, and tied to impact.' },
            { text: 'I\'ve noticed you\'ve been disengaged from the team lately.',
              isCorrect: false, feedback: 'Too vague and reads as a character judgement.' },
            { text: 'Your work has been below expectations for several weeks now.',
              isCorrect: false, feedback: 'No specifics — hard to act on.' },
          ]} />
        </>
      )}
    </div>
  );
}

function CorrectWrongFeedbackEditor() {
  return (
    <div style={{
      border: '1px solid var(--border)', borderRadius: 'var(--radius-md)',
      background: 'var(--surface-2)', overflow: 'hidden',
    }}>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 0 }}>
        <div style={{ padding: 12, borderRight: '1px solid var(--border)' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 6 }}>
            <I.CheckCircle size={13} style={{ color: 'var(--success)' }} />
            <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--success-text)' }}>Correct feedback (T4)</span>
          </div>
          <p style={{ margin: 0, fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.5 }}>
            Naming the specific behaviour without judgement is the SBI core move.
          </p>
        </div>
        <div style={{ padding: 12 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 6 }}>
            <I.X size={13} style={{ color: 'var(--error)' }} />
            <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--error-text)' }}>Wrong feedback (branch tab)</span>
          </div>
          <p style={{ margin: 0, fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.5 }}>
            That phrasing speaks to character rather than behaviour. Try naming a concrete action…
          </p>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { SurfaceLayoutEditor, LayoutEditorHeader, LayoutTypeSwitcher,
  EditorBlock, SequenceEditor, LayoutHeaderActions,
  KebabMenu, ConfirmDialog, DuplicateLayoutDialog });
