// Surface 2 — Draft (Modules + Layouts overview).
// Two columns: Modules · Layouts.
// (The right-hand AI suggestions panel was removed; suggestions live in the Layout editor.)

function SurfaceDraft({ course, selectedModuleId, selectedLayoutId, onSelectModule, onSelectLayout, onOpenLayoutEditor, onReorderModules, onReorderLayouts, onRenameGroup, onAddModule, onEditModule, onAddLayout, onDeleteLayout, onDeleteModule, onDeleteGroup, sources }) {
  const [showAddModule, setShowAddModule] = React.useState(false);
  const module = course.modules.find((m) => m.id === selectedModuleId) || course.modules[0];
  const layout = module.layouts.find((l) => l.id === selectedLayoutId) ||
  module.layouts.find((l) => l && l.selected) ||
  module.layouts[0] ||
  { id: '—' };
  const activeGroup = (course.moduleGroups || []).find((g) => g.id === module.group);

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

      <RequestedActions modules={course.modules} moduleGroups={course.moduleGroups} />

      <div style={{
        display: 'grid',
        gridTemplateColumns: '252px minmax(0, 1fr)',
        gridTemplateRows: 'minmax(0, 1fr)',
        gap: 0,
        flex: 1, minHeight: 0,
        overflow: 'hidden'
      }}>
        {/* ── Left column · Modules ───────────────────────────────── */}
        <ModulesColumn modules={course.modules}
        moduleGroups={course.moduleGroups}
        selectedId={module.id}
        onSelect={onSelectModule}
        onReorder={onReorderModules}
        onDeleteModule={onDeleteModule}
        onDeleteGroup={onDeleteGroup}
        onAddModuleClick={() => setShowAddModule(true)} />

        {/* ── Middle column · Layouts in selected module ──────────── */}
        <LayoutsColumn module={module}
        modules={course.modules}
        group={activeGroup}
        selectedId={layout.id}
        onSelect={onSelectLayout}
        onOpen={onOpenLayoutEditor}
        onReorder={onReorderLayouts}
        onRenameGroup={onRenameGroup}
        onEditModule={onEditModule}
        onAddLayout={onAddLayout}
        onDeleteLayout={onDeleteLayout} />

      </div>

      {showAddModule &&
      <AddModuleDialog
        course={course}
        sources={sources}
        onCancel={() => setShowAddModule(false)}
        onCreate={(payload) => {
          onAddModule && onAddModule(payload);
          setShowAddModule(false);
        }} />
      }
    </div>);

}

// ── Modules column ──────────────────────────────────────────────────────────
function ModulesColumn({ modules, moduleGroups, selectedId, onSelect, onReorder, onDeleteModule, onDeleteGroup, onAddModuleClick }) {
  const [dragId, setDragId] = React.useState(null);
  const [overId, setOverId] = React.useState(null);

  const handleDrop = (targetId) => {
    if (!dragId || dragId === targetId) {setDragId(null);setOverId(null);return;}
    const from = modules.findIndex((m) => m.id === dragId);
    const to = modules.findIndex((m) => m.id === targetId);
    if (from < 0 || to < 0) return;
    const target = modules[to];
    const next = modules.slice();
    const [moved] = next.splice(from, 1);
    // Keep group consistency: dropped module inherits target's group.
    moved.group = target.group;
    next.splice(to, 0, moved);
    onReorder && onReorder(next);
    setDragId(null);setOverId(null);
  };

  // Build group buckets in the order groups are declared. Modules without a
  // group are collected into an ‘(Ungrouped)’ bucket at the bottom — only
  // rendered if non-empty, so single-track courses look unchanged.
  const groups = (moduleGroups || []).map((g) => ({
    ...g, modules: modules.filter((m) => m.group === g.id)
  }));
  const ungrouped = modules.filter((m) => !m.group);
  if (ungrouped.length) groups.push({ id: '_', title: '(Ungrouped)', modules: ungrouped });

  return (
    <section style={{
      borderRight: '1px solid var(--border)',
      background: 'var(--surface)',
      display: 'flex', flexDirection: 'column',
      minHeight: 0, height: '100%', overflow: 'hidden'
    }} aria-label="Modules">
      <ColumnHeader title="Modules" />
      <div style={{ flex: 1, overflowY: 'auto', padding: '8px 8px 12px' }}>
        {groups.map((g, gi) =>
        <ModuleGroupBlock key={g.id}
        group={g}
        isFirst={gi === 0}
        selectedId={selectedId}
        dragId={dragId}
        overId={overId}
        onSelect={onSelect}
        setDragId={setDragId}
        setOverId={setOverId}
        handleDrop={handleDrop}
        onDeleteModule={onDeleteModule}
        onDeleteGroup={onDeleteGroup} />
        )}
        <button onClick={onAddModuleClick}
        className="btn sm ghost"
        style={{ width: '100%', marginTop: 12, height: 36,
          justifyContent: 'center', borderStyle: 'dashed',
          border: '1px dashed var(--border-strong)' }}
        onMouseOver={(e) => {
          e.currentTarget.style.borderColor = 'var(--accent)';
          e.currentTarget.style.color = 'var(--accent-text)';
          e.currentTarget.style.background = 'var(--accent-bg)';
        }}
        onMouseOut={(e) => {
          e.currentTarget.style.borderColor = 'var(--border-strong)';
          e.currentTarget.style.color = '';
          e.currentTarget.style.background = '';
        }}>
          <I.Plus size={13} />Add module
        </button>
      </div>
    </section>);

}

function ModuleGroupBlock({ group, isFirst, selectedId, dragId, overId, onSelect,
  setDragId, setOverId, handleDrop, onDeleteModule, onDeleteGroup }) {
  const [collapsed, setCollapsed] = React.useState(false);
  const [confirmDel, setConfirmDel] = React.useState(false);
  const showGroupChrome = group.id !== '_';
  const KebabMenu = window.KebabMenu;
  const ConfirmDialog = window.ConfirmDialog;
  return (
    <div style={{
      marginTop: isFirst ? 4 : 16,
      marginBottom: 4,
      ...(showGroupChrome && {
        background: 'var(--surface-inset)',
        borderRadius: 'var(--radius-md)',
        padding: '8px 6px 6px',
        border: '1px solid var(--border)'
      })
    }}>
      {/* Group header */}
      {showGroupChrome &&
      <div
        onClick={() => setCollapsed((c) => !c)}
        style={{
          display: 'flex', alignItems: 'center', gap: 6,
          padding: '4px 8px',
          marginBottom: collapsed ? 0 : 6,
          cursor: 'default',
          borderRadius: 4,
          userSelect: 'none'
        }}>
          <I.ChevronDown size={11}
        style={{ color: 'var(--text-muted)',
          transform: collapsed ? 'rotate(-90deg)' : 'none',
          transition: 'transform 120ms' }} />
          <I.Folder size={12} style={{ color: 'var(--text-muted)' }} />
          <span style={{
          fontSize: 11.5, fontWeight: 700,
          color: 'var(--text)',
          letterSpacing: '.01em',
          flex: 1, minWidth: 0,
          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'
        }} title={locText(group.title)}>{locText(group.title)}</span>
          <span style={{ fontSize: 10,
          fontFamily: 'var(--font-mono)',
          background: 'var(--surface)',
          padding: '1px 5px', borderRadius: 3, color: "rgb(99, 117, 141)" }}>{group.modules.length}</span>
          <span onClick={(e) => e.stopPropagation()} style={{ display: 'inline-flex' }}>
            <KebabMenu items={[
              { label: 'Delete chapter', icon: 'Trash', danger: true,
                hint: 'Removes the chapter and all its modules',
                onSelect: () => setConfirmDel(true) },
            ]} />
          </span>
        </div>
      }
      {!collapsed &&
      <div style={{ padding: showGroupChrome ? '0 2px' : 0 }}>
          {group.modules.map((m, i) =>
        <React.Fragment key={m.id}>
              {i > 0 &&
          <div style={{ height: 1,
            background: showGroupChrome ? 'var(--border-faint)' : 'var(--border)',
            margin: '6px 4px' }} />
          }
              <ModuleCard module={m}
          inGroup={showGroupChrome}
          selected={m.id === selectedId}
          dragging={dragId === m.id}
          dragOver={overId === m.id && dragId !== m.id}
          onClick={() => onSelect(m.id)}
          onDelete={onDeleteModule}
          onDragStart={() => setDragId(m.id)}
          onDragOver={(e) => {e.preventDefault();setOverId(m.id);}}
          onDragLeave={() => {if (overId === m.id) setOverId(null);}}
          onDrop={() => handleDrop(m.id)}
          onDragEnd={() => {setDragId(null);setOverId(null);}} />
            </React.Fragment>
        )}
        </div>
      }
      {confirmDel && ConfirmDialog &&
        <ConfirmDialog icon="Trash" tone="danger"
          title={`Delete “${locText(group.title)}”?`}
          body={`This removes the chapter and its ${group.modules.length} module${group.modules.length === 1 ? '' : 's'} (and every layout inside them). The source paragraphs they covered will be marked unmapped in Validation.`}
          confirmLabel="Delete chapter"
          onCancel={() => setConfirmDel(false)}
          onConfirm={() => { setConfirmDel(false); onDeleteGroup && onDeleteGroup(group.id); }} />
      }
    </div>);

}

// ── Editable group breadcrumb shown above the module title ────────────────────────
function GroupBreadcrumb({ group, onRename }) {
  const [editing, setEditing] = React.useState(false);
  const src = locText(group.title);
  const [value, setValue] = React.useState(src);
  React.useEffect(() => {setValue(src);}, [src]);

  const commit = () => {
    setEditing(false);
    const v = value.trim();
    if (v && v !== src) onRename && onRename(group.id, v);else
    setValue(src);
  };

  if (editing) {
    return (
      <input autoFocus value={value} onChange={(e) => setValue(e.target.value)}
      onBlur={commit}
      onKeyDown={(e) => {
        if (e.key === 'Enter') commit();
        if (e.key === 'Escape') {setValue(src);setEditing(false);}
      }}
      className="field"
      style={{ height: 22, fontSize: 11, padding: '0 6px',
        fontWeight: 600, letterSpacing: '.02em' }} />);

  }
  return (
    <button onClick={() => setEditing(true)}
    title="Click to rename group"
    style={{
      display: 'inline-flex', alignItems: 'center', gap: 5,
      padding: '2px 6px', margin: '-2px -6px 0',
      background: 'transparent', border: 0,
      borderRadius: 4,
      color: 'var(--text-muted)',
      fontFamily: 'inherit', cursor: 'default',
      alignSelf: 'flex-start'
    }}
    onMouseOver={(e) => e.currentTarget.style.background = 'var(--surface-inset)'}
    onMouseOut={(e) => e.currentTarget.style.background = 'transparent'}>
      <I.Folder size={11} style={{ color: 'var(--text-faint)' }} />
      <span style={{ fontSize: 11, fontWeight: 600,
        letterSpacing: '.04em', textTransform: 'uppercase',
        color: 'var(--text-muted)' }}>
        {locText(group.title)}
      </span>
      <I.Edit size={9} style={{ color: 'var(--text-faint)', opacity: 0.6 }} />
    </button>);

}

function ModuleCard({ module: m, inGroup, selected, dragging, dragOver,
  onClick, onDelete, onDragStart, onDragOver, onDragLeave, onDrop, onDragEnd }) {
  const layoutStatuses = m.layouts.map((l) => l.status);
  const hasIssues = layoutStatuses.includes('issues');
  const hasPending = layoutStatuses.includes('pending');
  const validationLevel = hasIssues ? 'issues' : hasPending ? 'pending' : 'accepted';
  const [gripHover, setGripHover] = React.useState(false);
  const [confirmDel, setConfirmDel] = React.useState(false);
  const KebabMenu = window.KebabMenu;
  const ConfirmDialog = window.ConfirmDialog;
  return (
    <div
      draggable
      onClick={onClick}
      onDragStart={(e) => {
        e.dataTransfer.effectAllowed = 'move';
        try {e.dataTransfer.setData('text/plain', m.id);} catch (_) {}
        onDragStart && onDragStart();
      }}
      onDragOver={onDragOver}
      onDragLeave={onDragLeave}
      onDrop={onDrop}
      onDragEnd={onDragEnd}
      style={{
        display: 'block', width: '100%', textAlign: 'left',
        padding: '10px 11px',
        margin: '0 0 4px',
        borderRadius: 'var(--radius-md)',

        borderColor: dragOver ? 'var(--accent)' :
        selected ? 'var(--accent-border)' : 'transparent',
        background: selected ? 'var(--accent-bg)' : inGroup ? 'var(--surface)' : 'transparent',
        boxShadow: dragOver ? '0 -2px 0 0 var(--accent)' : 'none',
        cursor: gripHover ? 'grab' : 'default',
        opacity: dragging ? 0.4 : 1,
        fontFamily: 'inherit', color: 'var(--text)',
        position: 'relative',
        userSelect: 'none',
        transition: 'border-color 80ms, box-shadow 80ms, opacity 80ms', border: "1px solid rgb(99, 117, 141)"
      }}
      onMouseOver={(e) => {if (!selected && !dragOver) e.currentTarget.style.background = 'var(--surface-inset)';}}
      onMouseOut={(e) => {if (!selected) e.currentTarget.style.background = inGroup ? 'var(--surface)' : 'transparent';}}>
      {selected &&
      <span style={{
        position: 'absolute', left: -1, top: 8, bottom: 8, width: 3,
        background: 'var(--accent)', borderRadius: '0 2px 2px 0'
      }} />
      }
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
        <span
          onMouseEnter={() => setGripHover(true)}
          onMouseLeave={() => setGripHover(false)}
          title="Drag to reorder"
          style={{ marginLeft: -4, display: 'inline-flex', cursor: 'grab',
            color: 'var(--text-faint)', opacity: selected || gripHover ? 1 : 0.55 }}>
          <I.GripVertical size={13} />
        </span>
        <span style={{
          fontFamily: 'var(--font-mono)', fontSize: 10.5, fontWeight: 600,
          padding: '1px 5px', borderRadius: 3,
          background: selected ? 'var(--surface)' : 'var(--surface-inset)',
          color: 'var(--text-muted)', letterSpacing: '.04em'
        }}>M{m.n}</span>
        <StatusPill iconOnly status={validationLevel === 'accepted' ? 'accepted' : validationLevel === 'issues' ? 'issues' : 'pending'} />
        <div style={{ flex: 1 }} />
        <span onClick={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()}
          draggable={false} onDragStart={(e) => { e.preventDefault(); e.stopPropagation(); }}
          style={{ display: 'inline-flex' }}>
          <KebabMenu items={[
            { label: 'Delete module', icon: 'Trash', danger: true,
              hint: 'Removes this module and all its layouts',
              onSelect: () => setConfirmDel(true) },
          ]} />
        </span>
      </div>
      <div style={{ fontSize: 13.5, fontWeight: 600, color: 'var(--text)', marginBottom: 2,
        lineHeight: 1.3 }}>{locText(m.title)}</div>
      <div style={{ fontSize: 11.5, color: 'var(--text-muted)', display: 'flex', gap: 10, alignItems: 'center' }}>
        <span>{m.layouts.length} layouts</span>
        <span>·</span>
        <span style={{ fontFamily: 'var(--font-mono)' }}>{m.duration}</span>
      </div>
      {confirmDel && ConfirmDialog &&
        <ConfirmDialog icon="Trash" tone="danger"
          title={`Delete ${m.id} — “${locText(m.title)}”?`}
          body={`This removes the module and its ${m.layouts.length} layout${m.layouts.length === 1 ? '' : 's'}. The source paragraphs it covered will be marked unmapped in Validation.`}
          confirmLabel="Delete module"
          onCancel={() => setConfirmDel(false)}
          onConfirm={() => { setConfirmDel(false); onDelete && onDelete(m.id); }} />
      }
    </div>);

}

// ── Layouts column ──────────────────────────────────────────────────────────
function LayoutsColumn({ module, modules, group, selectedId, onSelect, onOpen, onReorder, onRenameGroup, onEditModule, onAddLayout, onDeleteLayout }) {
  const [dragId, setDragId] = React.useState(null);
  const [overId, setOverId] = React.useState(null);

  const handleDrop = (targetId) => {
    if (!dragId || dragId === targetId) {setDragId(null);setOverId(null);return;}
    const from = module.layouts.findIndex((l) => l.id === dragId);
    const to = module.layouts.findIndex((l) => l.id === targetId);
    if (from < 0 || to < 0) return;
    const next = module.layouts.slice();
    const [moved] = next.splice(from, 1);
    next.splice(to, 0, moved);
    onReorder && onReorder(module.id, next);
    setDragId(null);setOverId(null);
  };

  return (
    <section style={{
      background: 'var(--bg)',
      display: 'flex', flexDirection: 'column',
      minHeight: 0, height: '100%', overflow: 'hidden'
    }} aria-label="Layouts in selected module">
      <div style={{ flex: 1, overflowY: 'auto',
        padding: '16px 16px 16px',
        display: 'flex', flexDirection: 'column', gap: 12 }}>

        {/* Module info card — same visual treatment as a layout row, so
            it reads as a distinct element sitting inside the column. */}
        <div style={{
          background: 'var(--surface)',
          border: '1px solid var(--border)',
          borderRadius: 'var(--radius-md)',
          padding: '12px 14px',
          display: 'flex', gap: 12, alignItems: 'stretch',
        }}>
          {/* Module thumbnail — emitted as <thumbnailURL> in the package. */}
          <ModuleThumb value={module.thumbnailUrl} moduleId={module.id}
            onChange={(url) => onEditModule && onEditModule(module.id, { thumbnailUrl: url })} />
          <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 6 }}>
            {group && (
              <GroupBreadcrumb group={group} onRename={onRenameGroup} />
            )}
            <div style={{ display: 'flex', alignItems: 'center', gap: 8,
              minWidth: 0 }}>
              <span style={{
                fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 600,
                padding: '1px 5px', borderRadius: 3,
                background: 'var(--surface-inset)',
                color: 'var(--text-muted)', letterSpacing: '.04em',
                flexShrink: 0,
              }}>{module.id}</span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <ModuleInlineEdit
                  value={module.title}
                  placeholder="Module title…"
                  onChange={(v) => onEditModule && onEditModule(module.id, { title: locSet(module.title, 'en', v) })}
                  fontSize={14}
                  fontWeight={600}
                  allowEmpty={false}
                  title="Click to rename module" />
              </div>
              <AddLayoutPicker variant="header"
                onPick={(type) => onAddLayout && onAddLayout(module.id, type)} />
            </div>
            <ModuleInlineEdit
              value={module.summary || ''}
              placeholder="Add a one-line summary…"
              onChange={(v) => onEditModule && onEditModule(module.id, { summary: locSet(module.summary, 'en', v) })}
              fontSize={12.5}
              fontWeight={400}
              color="var(--text-muted)"
              allowEmpty={true}
              multiline
              title="Click to edit summary" />
          </div>
        </div>

        {/* Layout rows */}
        <div style={{ display: 'grid', gap: 4 }}>
          {module.layouts.map((l, i) =>
          <LayoutRow key={l.id} layout={l}
          module={module}
          modules={modules}
          selected={l.id === selectedId}
          dragging={dragId === l.id}
          dragOver={overId === l.id && dragId !== l.id}
          onClick={() => onOpen(l.id)}
          onDoubleClick={() => onOpen(l.id)}
          onDelete={onDeleteLayout}
          onDragStart={() => setDragId(l.id)}
          onDragOver={(e) => {e.preventDefault();setOverId(l.id);}}
          onDragLeave={() => {if (overId === l.id) setOverId(null);}}
          onDrop={() => handleDrop(l.id)}
          onDragEnd={() => {setDragId(null);setOverId(null);}} />
          )}
          <AddLayoutPicker variant="full"
            onPick={(type) => onAddLayout && onAddLayout(module.id, type)} />
        </div>
      </div>
    </section>);

}

function LayoutRow({ layout, module, modules, selected, dragging, dragOver,
  onClick, onDoubleClick, onDelete,
  onDragStart, onDragOver, onDragLeave, onDrop, onDragEnd }) {
  const meta = (window.LAYOUT_TYPES || []).find((t) => t.id === layout.type) || {};
  const Icon = I[meta.icon] || I.Hash;
  const friendly = (layout.type || '').replace(/_/g, ' ');
  const [gripHover, setGripHover] = React.useState(false);
  const [confirmDel, setConfirmDel] = React.useState(false);
  const [duplicating, setDuplicating] = React.useState(false);
  const KebabMenu = window.KebabMenu;
  const ConfirmDialog = window.ConfirmDialog;
  const DuplicateLayoutDialog = window.DuplicateLayoutDialog;
  return (
    <div onClick={onClick} onDoubleClick={onDoubleClick}
    draggable
    onDragStart={(e) => {
      e.dataTransfer.effectAllowed = 'move';
      try {e.dataTransfer.setData('text/plain', layout.id);} catch (_) {}
      onDragStart && onDragStart();
    }}
    onDragOver={onDragOver}
    onDragLeave={onDragLeave}
    onDrop={onDrop}
    onDragEnd={onDragEnd}
    style={{
      display: 'grid',
      gridTemplateColumns: 'auto 36px minmax(160px, max-content) minmax(0, 1fr) auto auto',
      gap: 10, alignItems: 'center',
      padding: '8px 10px',
      background: selected ? 'var(--accent-bg)' : 'var(--surface)',
      border: '1px solid',
      borderColor: dragOver ? 'var(--accent)' :
      selected ? 'var(--accent-border)' : 'var(--border)',
      borderRadius: 'var(--radius-md)',
      cursor: gripHover ? 'grab' : 'default',
      position: 'relative',
      boxShadow: dragOver ? '0 -2px 0 0 var(--accent)' :
      selected ? '0 0 0 1px var(--accent-border)' : 'none',
      opacity: dragging ? 0.4 : 1,
      userSelect: 'none',
      transition: 'border-color 80ms, box-shadow 80ms, opacity 80ms'
    }}
    onMouseOver={(e) => {if (!selected && !dragOver) e.currentTarget.style.borderColor = 'var(--border-strong)';}}
    onMouseOut={(e) => {if (!selected && !dragOver) e.currentTarget.style.borderColor = 'var(--border)';}}>
      <span
        onMouseEnter={() => setGripHover(true)}
        onMouseLeave={() => setGripHover(false)}
        onClick={(e) => e.stopPropagation()}
        title="Drag to reorder"
        style={{ display: 'inline-flex', cursor: 'grab', color: 'var(--text-faint)' }}>
        <I.GripVertical size={14} />
      </span>
      <span style={{
        fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 600,
        color: 'var(--text-muted)', letterSpacing: '.04em'
      }}>L{layout.n}</span>
      <span className="chip" style={{ whiteSpace: 'nowrap' }}>
        <Icon size={11} />{friendly}
      </span>
      <span className="truncate" style={{
        fontSize: 13, color: 'var(--text)', minWidth: 0
      }}>{locText(layout.summary)}</span>
      <StatusPill status={layout.status} />
      <span onClick={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()}
        draggable={false} onDragStart={(e) => { e.preventDefault(); e.stopPropagation(); }}
        style={{ display: 'inline-flex' }}>
        <KebabMenu items={[
          { label: 'Open in Layout editor', icon: 'ArrowRight', onSelect: onDoubleClick },
          { label: 'Duplicate layout', icon: 'Copy',
            hint: 'Copy this layout into another module',
            onSelect: () => setDuplicating(true) },
          { divider: true },
          { label: 'Delete layout', icon: 'Trash', danger: true,
            onSelect: () => setConfirmDel(true) },
        ]} />
      </span>
      {confirmDel && ConfirmDialog &&
        <span onClick={(e) => e.stopPropagation()}>
          <ConfirmDialog icon="Trash" tone="danger"
            title={`Delete layout L${layout.n}?`}
            body="This removes the layout from the module. The source paragraphs it covered will be marked unmapped and surface in Validation."
            confirmLabel="Delete layout"
            onCancel={() => setConfirmDel(false)}
            onConfirm={() => { setConfirmDel(false); onDelete && onDelete(layout.id); }} />
        </span>
      }
      {duplicating && DuplicateLayoutDialog &&
        <span onClick={(e) => e.stopPropagation()}>
          <DuplicateLayoutDialog
            layout={layout}
            sourceModule={module}
            modules={modules || []}
            onCancel={() => setDuplicating(false)}
            onConfirm={() => setDuplicating(false)} />
        </span>
      }
    </div>);

}

// ── Shared column header ────────────────────────────────────────────────────
function ColumnHeader({ title, subtitle, count, action }) {
  return (
    <div style={{
      display: 'flex', alignItems: 'flex-start', gap: 8,
      padding: '12px 16px',
      borderBottom: '1px solid var(--border)',
      background: 'var(--surface)',
      minHeight: 48
    }}>
      <div style={{ flex: 1, display: 'flex', alignItems: 'flex-start', gap: 8, minWidth: 0 }}>
        <h2 style={{
          margin: 0, fontSize: 13.5, fontWeight: 600,
          color: 'var(--text)', letterSpacing: '-.01em',
          minWidth: 0, flex: 1
        }}>{title}</h2>
        {count != null && <span style={{
          fontSize: 11, color: 'var(--text-faint)', fontFamily: 'var(--font-mono)',
          background: 'var(--surface-inset)', padding: '2px 6px', borderRadius: 4
        }}>{count}</span>}
        {subtitle && <span style={{ fontSize: 12, color: 'var(--text-faint)' }}>{subtitle}</span>}
      </div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 4, paddingTop: 2 }}>{action}</div>
    </div>);

}

// ── Click-to-edit inline editor used by the Layouts column header for the
//    module title + summary. Empty values show a placeholder; click anywhere
//    on the label enters edit mode. Enter (or blur) commits, Escape cancels.
//    Set `multiline` for summary-style fields (Shift+Enter for line breaks,
//    Enter alone still commits).
function ModuleInlineEdit({ value, placeholder, onChange, fontSize = 13.5,
  fontWeight = 500, color = 'var(--text)',
  allowEmpty = true, multiline = false, title }) {
  const [editing, setEditing] = React.useState(false);
  // `value` is a LocalizedString (per-language object) or legacy string. Edit
  // the active-language slot; the parent's onChange wraps the typed string back
  // into the object via locSet (see call sites).
  const str = locText(value);
  const [draft, setDraft] = React.useState(str);
  React.useEffect(() => {setDraft(str);}, [str]);

  const commit = () => {
    setEditing(false);
    const v = (draft || '').trim();
    if (v === str.trim()) return;
    if (!v && !allowEmpty) {setDraft(str);return;}
    onChange && onChange(v);
  };

  if (editing) {
    const Comp = multiline ? 'textarea' : 'input';
    return (
      <Comp autoFocus value={draft}
      onChange={(e) => setDraft(e.target.value)}
      onBlur={commit}
      onKeyDown={(e) => {
        if (e.key === 'Enter' && !(multiline && e.shiftKey)) {
          e.preventDefault();commit();
        } else if (e.key === 'Escape') {
          setDraft(str);setEditing(false);
        }
      }}
      rows={multiline ? 2 : undefined}
      className="field"
      style={{
        width: '100%', minWidth: 0,
        fontSize, fontWeight, color,
        fontFamily: 'inherit',
        height: multiline ? 'auto' : 26,
        padding: multiline ? '4px 6px' : '0 6px',
        minHeight: multiline ? 36 : undefined,
        resize: multiline ? 'vertical' : 'none',
        lineHeight: 1.4
      }} />);

  }

  const isEmpty = !str.trim();
  return (
    <span
      onClick={() => setEditing(true)}
      title={title}
      style={{
        display: multiline ? '-webkit-box' : 'inline-block',
        width: '100%', minWidth: 0,
        padding: '2px 6px', margin: '-2px -6px',
        borderRadius: 4,
        fontSize, fontWeight,
        color: isEmpty ? 'var(--text-faint)' : color,
        fontStyle: isEmpty ? 'italic' : 'normal',
        lineHeight: 1.4,
        cursor: 'text',
        overflow: 'hidden',
        textOverflow: 'ellipsis',
        whiteSpace: multiline ? 'normal' : 'nowrap',
        ...(multiline && {
          WebkitLineClamp: 2,
          WebkitBoxOrient: 'vertical'
        }),
        transition: 'background 80ms'
      }}
      onMouseOver={(e) => e.currentTarget.style.background = 'var(--surface-inset)'}
      onMouseOut={(e) => e.currentTarget.style.background = 'transparent'}>
      {isEmpty ? placeholder : str}
    </span>);

}

// ── Add-layout picker ────────────────────────────────────────────────────────
// A compact popover listing every (non-hidden) layout type, grouped. Used by
// the module-header "Add layout" button and the dashed add row at the bottom of
// the layout list. Picking a type appends a pending layout to the module.
function AddLayoutPicker({ variant = 'header', onPick }) {
  const [open, setOpen] = React.useState(false);
  const types = (window.LAYOUT_TYPES || []).filter((t) => !t.hidden);
  const groups = {};
  types.forEach((t) => {(groups[t.group] = groups[t.group] || []).push(t);});

  const trigger = variant === 'full' ?
  <button className="btn sm ghost" onClick={() => setOpen((o) => !o)}
    style={{ width: '100%', height: 36, justifyContent: 'center',
      borderStyle: 'dashed', border: '1px dashed var(--border-strong)', marginTop: 4 }}>
      <I.Plus size={12} />Add layout
    </button> :
  <button className="btn sm" onClick={() => setOpen((o) => !o)} style={{ flexShrink: 0 }}>
      <I.Plus size={12} />Add layout
    </button>;

  return (
    <div style={{ position: 'relative', ...(variant === 'full' && { width: '100%' }) }}>
      {trigger}
      {open &&
      <>
        <div onClick={() => setOpen(false)}
        style={{ position: 'fixed', inset: 0, zIndex: 40 }} />
        <div style={{
          position: 'absolute', zIndex: 41, right: 0, width: 300,
          ...(variant === 'full' ? { bottom: 'calc(100% + 4px)' } : { top: 'calc(100% + 4px)' }),
          background: 'var(--surface)', border: '1px solid var(--border)',
          borderRadius: 'var(--radius-md)', boxShadow: 'var(--shadow-lg)',
          padding: 8, maxHeight: 360, overflowY: 'auto'
        }}>
          <div style={{ padding: '6px 8px 4px', fontSize: 11, color: 'var(--text-faint)',
            fontWeight: 600, letterSpacing: '.04em', textTransform: 'uppercase' }}>
            Add a layout
          </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;
                return (
                  <button key={t.id}
                  onClick={() => {onPick && onPick(t.id);setOpen(false);}}
                  style={{
                    display: 'flex', alignItems: 'center', gap: 8, width: '100%',
                    padding: '6px 8px', textAlign: 'left', border: 0,
                    borderRadius: 'var(--radius)', background: 'transparent',
                    color: 'var(--text)', cursor: 'default', fontSize: 12.5, fontFamily: 'inherit'
                  }}
                  onMouseOver={(e) => e.currentTarget.style.background = 'var(--surface-inset)'}
                  onMouseOut={(e) => e.currentTarget.style.background = 'transparent'}>
                    <Ti size={14} style={{ color: 'var(--text-muted)' }} />
                    <span style={{ flex: 1 }}>{t.label}</span>
                  </button>);

              })}
            </div>
          )}
        </div>
      </>
      }
    </div>);

}

// ── Module thumbnail ─────────────────────────────────────────────────────────
// The module image emitted as <thumbnailURL>modules/<id>/image.jpg in the
// package. Empty → a dashed "Add image" tile; filled → a striped placeholder
// with the filename and a remove button. (Upload is stubbed to a placeholder
// path pending the asset pipeline.)
function ModuleThumb({ value, moduleId, onChange }) {
  const path = `modules/${(moduleId || 'module').toLowerCase()}/image.jpg`;
  if (value) {
    return (
      <div style={{ position: 'relative', width: 104, flexShrink: 0, borderRadius: 6,
        overflow: 'hidden', border: '1px solid var(--border)',
        background: 'linear-gradient(135deg, #475569, #1e293b)' }}>
        <div style={{ position: 'absolute', inset: 0, display: 'flex',
          alignItems: 'center', justifyContent: 'center', color: 'rgba(255,255,255,.5)' }}>
          <I.Image size={20} />
        </div>
        <div className="truncate" style={{ position: 'absolute', left: 0, right: 0, bottom: 0,
          padding: '3px 6px', fontSize: 9.5, fontFamily: 'var(--font-mono)',
          color: '#fff', background: 'linear-gradient(to top, rgba(0,0,0,.75), transparent)' }}>
          {String(value).split('/').pop()}
        </div>
        <button title="Remove image" onClick={() => onChange && onChange(null)}
          style={{ position: 'absolute', top: 4, right: 4, width: 18, height: 18,
            borderRadius: '50%', border: 0, cursor: 'default',
            background: 'rgba(15,23,42,.7)', color: '#fff',
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
          <I.X size={11} />
        </button>
      </div>);

  }
  return (
    <button title="Add module image" onClick={() => onChange && onChange(path)}
      style={{ width: 104, flexShrink: 0, minHeight: 64, borderRadius: 6,
        border: '1px dashed var(--border-strong)', background: 'var(--surface-inset)',
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
        gap: 4, color: 'var(--text-muted)', cursor: 'default', fontFamily: 'inherit' }}>
      <I.Image size={17} style={{ color: 'var(--text-faint)' }} />
      <span style={{ fontSize: 10.5, fontWeight: 500 }}>Add image</span>
      <span style={{ fontSize: 9, color: 'var(--text-faint)' }}>16 : 9</span>
    </button>);

}

Object.assign(window, { SurfaceDraft, ColumnHeader, ModuleInlineEdit });