// ─── Add Module dialog ─────────────────────────────────────────────────────
// Opened from the "+ Add module" button at the bottom of the Modules column
// on Course architecture.
//
// Lets the author:
//   · name a new module
//   · place it in an existing Module Group, create a new group, or leave
//     it ungrouped
//   · optionally write a short summary
//   · choose how to seed the module's layouts:
//       – Blank             → no layouts, fill in later
//       – Generate with AI  → AI drafts ~4–6 layouts from a description
//                             (and optionally a bound source file)
//       – Duplicate         → clone an existing module's layout shape
//
// The dialog is self-contained UI; commit flows back up through onCreate(payload)
// and the App-level state in app.jsx writes the module + group additions.

// SEED MODE 'ai' IS DELIBERATELY NOT SELECTABLE (2026-07-28). It used to be the
// DEFAULT, and it ran `fakeGenerateLayouts` — a mock that fabricated layouts of
// type `question` / `mandatoryQuestion`. Neither type exists in
// LayoutContentSchema, so the payload layout `{type:'question'}` produced
// `invalid_union_discriminator`, which the gateway's permissive save does NOT
// tolerate (services/gateway/src/routes/courses.ts isUnfinishedFieldIssue admits
// only missing values and too_small). One generated module therefore made EVERY
// subsequent `PUT /draft` of the WHOLE course return 400 — the first action a
// from-scratch author takes, poisoning the course permanently.
// PR #84 removed the other two mock generators for fabricating content into
// author fields; this one was missed because it sat behind the default option.
// The row stays visible as an honest preview of the real ingestion pipeline
// (which does not exist yet — no source-intake path exists at all), and the
// generator is gone. Do not re-add a client-side generator: layouts must come
// from a server that validates against the real schema.
function AddModuleDialog({ course, sources, defaultSeedMode = 'blank', onCancel, onCreate }) {
  const [title, setTitle] = React.useState('');
  const [summary, setSummary] = React.useState('');

  // ── Group placement ─────────────────────────────────────────────────────
  // groupId: id of an existing group, or '' for ungrouped, or '__new__' for
  // a freshly-typed group title.
  const [groupId, setGroupId] = React.useState(() => {
    // Pre-select the group the user is currently editing in, if any.
    const cur = course.modules[course.modules.length - 1];
    return cur && cur.group ? cur.group : course.moduleGroups[0]?.id || '';
  });
  const [newGroupTitle, setNewGroupTitle] = React.useState('');

  // ── Seed mode ───────────────────────────────────────────────────────────
  // 'ai' is never reachable here — the option is disabled (see the note above).
  const [seedMode, setSeedMode] = React.useState(
    defaultSeedMode === 'ai' ? 'blank' : defaultSeedMode);
  const [duplicateId, setDuplicateId] = React.useState(course.modules[0]?.id || '');

  // Build group buckets for the picker.
  const groupBuckets = course.moduleGroups.map((g) => ({
    ...g, count: course.modules.filter((m) => m.group === g.id).length
  }));
  const ungroupedCount = course.modules.filter((m) => !m.group).length;

  const isNewGroup = groupId === '__new__';
  const newGroupReady = !isNewGroup || newGroupTitle.trim().length > 0;
  const titleReady = title.trim().length > 0;
  const seedReady =
  seedMode === 'blank' ? true :
  seedMode === 'duplicate' ? !!duplicateId :
  false;
  const canSubmit = titleReady && newGroupReady && seedReady;

  const primaryLabel =
  seedMode === 'duplicate' ? 'Duplicate & add' :
  'Add module';

  // Esc to dismiss.
  React.useEffect(() => {
    const h = (e) => {if (e.key === 'Escape') onCancel();};
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, [onCancel]);

  const handleSubmit = () => {
    if (!canSubmit) return;
    const finalize = (layouts) => {
      onCreate({
        title: title.trim(),
        summary: summary.trim(),
        groupId: isNewGroup ? null : groupId || null,
        newGroupTitle: isNewGroup ? newGroupTitle.trim() : null,
        layouts,
        seedMode
      });
    };
    if (seedMode === 'duplicate') {
      const m = course.modules.find((x) => x.id === duplicateId);
      const layouts = (m?.layouts || []).map((l, i) => ({
        ...l, id: `_new_L${i + 1}`, status: 'pending',
        summary: l.summary // keep summary for now; user can re-roll later
      }));
      finalize(layouts);
    } else {
      finalize([]);
    }
  };

  return (
    <div onClick={onCancel} style={{
      position: 'fixed', inset: 0, zIndex: 70,
      background: 'color-mix(in oklab, var(--text) 35%, transparent)',
      backdropFilter: 'blur(2px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      padding: 24
    }}>
      <div onClick={(e) => e.stopPropagation()} className="slide-in" style={{
        width: 640, maxWidth: '100%', maxHeight: 'calc(100vh - 48px)',
        background: 'var(--surface)', border: '1px solid var(--border)',
        borderRadius: 'var(--radius-md)', boxShadow: 'var(--shadow-xl)',
        display: 'flex', flexDirection: 'column', overflow: 'hidden'
      }}>

        {/* ── Header ───────────────────────────────────────────────── */}
        <div style={{
          padding: '16px 20px',
          borderBottom: '1px solid var(--border)',
          display: 'flex', alignItems: 'center', gap: 12
        }}>
          <span style={{
            width: 30, height: 30, borderRadius: 8,
            background: 'var(--accent-bg)', color: 'var(--accent)',
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center'
          }}>
            <I.Layers size={15} />
          </span>
          <div style={{ flex: 1, minWidth: 0 }}>
            <h2 style={{ margin: 0, fontSize: 14.5, fontWeight: 600 }}>Add module</h2>
            <div style={{ fontSize: 12, color: 'var(--text-muted)' }}>
              New module · M{course.modules.length + 1}{' '}
              <span style={{ color: 'var(--text-faint)' }}>·</span>{' '}
              <span style={{ color: 'var(--text-muted)' }}>{course.title}</span>
            </div>
          </div>
          <button className="btn sm ghost" onClick={onCancel}
          style={{ width: 28, height: 28, padding: 0, justifyContent: 'center' }}>
            <I.X size={13} />
          </button>
        </div>

        {/* ── Body (scrolls if tall) ───────────────────────────────── */}
        <div style={{
          flex: 1, overflowY: 'auto',
          padding: '16px 20px',
          display: 'flex', flexDirection: 'column', gap: 16
        }}>
          {/* Title + summary block */}
          <div style={{ display: 'grid', gap: 12 }}>
            <AMField label="Module title" required>
              <input value={title} autoFocus
              onChange={(e) => setTitle(e.target.value)}
              placeholder="e.g. After-action reviews"
              className="field"
              style={{ width: '100%', fontSize: 14, height: 36 }} />
            </AMField>
            <AMField label="Summary">
              <textarea value={summary} rows={2}
              onChange={(e) => setSummary(e.target.value)}
              placeholder="One line on what the learner should walk away with."
              className="field"
              style={{ width: '100%', fontSize: 13, lineHeight: 1.5,
                resize: 'vertical', minHeight: 40 }} />
            </AMField>
          </div>

          {/* Group placement */}
          <AMField label="Module group">
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 2 }}>
              <GroupChip
                icon={<I.Minus size={11} />}
                label="None"
                hint={ungroupedCount > 0 ? `${ungroupedCount} module${ungroupedCount === 1 ? '' : 's'}` : 'ungrouped'}
                selected={groupId === ''}
                onClick={() => setGroupId('')} />
              {groupBuckets.map((g) =>
              <GroupChip key={g.id}
              icon={<I.Folder size={11} />}
              label={locText(g.title)}
              selected={groupId === g.id}
              onClick={() => setGroupId(g.id)} />
              )}
              <GroupChip
                icon={<I.Plus size={11} />}
                label="New group"
                selected={isNewGroup}
                accent
                onClick={() => setGroupId('__new__')} />
            </div>
            {isNewGroup &&
            <div style={{
              marginTop: 10, display: 'flex', alignItems: 'center', gap: 8,
              padding: '10px 12px',
              background: 'var(--surface-inset)',
              border: '1px solid var(--border)',
              borderRadius: 'var(--radius-md)'
            }}>
                <I.Folder size={13} style={{ color: 'var(--text-muted)' }} />
                <input value={newGroupTitle} autoFocus
              onChange={(e) => setNewGroupTitle(e.target.value)}
              placeholder="Group name — e.g. Difficult escalations"
              className="field"
              style={{ flex: 1, height: 30, background: 'var(--surface)',
                fontSize: 13, fontWeight: 500 }} />
                <span style={{ fontSize: 11, color: 'var(--text-faint)',
                fontFamily: 'var(--font-mono)' }}>
                  +1 group
                </span>
              </div>
            }
          </AMField>

          {/* Seed mode */}
          <AMField label="Seed with">
            <div style={{ display: 'grid', gap: 8, marginTop: 2 }}>
              <SeedOption
                seedKey="blank"
                selected={seedMode === 'blank'}
                onSelect={() => setSeedMode('blank')}
                icon={<I.FileText size={14} />}
                title="Blank module"
                desc="" />

              {/* Honest preview. Not selectable: generating layouts requires a
                  server that validates against the real schema, and no
                  source-intake path exists yet. See the note at the top. */}
              <SeedOption
                seedKey="ai"
                selected={false}
                disabled
                onSelect={null}
                icon={<I.Sparkle size={14} />}
                ai
                title="Generate with AI"
                desc="">
                <div style={{ marginTop: 8 }}>
                  {window.PreviewNote
                    ? <window.PreviewNote>Not available yet — generating a module from your sources needs the ingestion pipeline, which isn’t built. Add a blank module and author it, or duplicate an existing one.</window.PreviewNote>
                    : null}
                </div>
              </SeedOption>

              <SeedOption
                seedKey="duplicate"
                selected={seedMode === 'duplicate'}
                onSelect={() => setSeedMode('duplicate')}
                icon={<I.Copy size={14} />}
                title="Duplicate from module"
                desc="">
                {seedMode === 'duplicate' &&
                <div style={{ marginTop: 10, display: 'flex', alignItems: 'center', gap: 8 }}>
                    <I.Copy size={12} style={{ color: 'var(--text-muted)' }} />
                    <span style={{ fontSize: 11.5, color: 'var(--text-muted)' }}>
                      Source module
                    </span>
                    <select value={duplicateId}
                      onChange={(e) => setDuplicateId(e.target.value)}
                      onClick={(e) => e.stopPropagation()}
                      className="field"
                      style={{ flex: 1, height: 30, fontSize: 12.5 }}>
                      {(() => {
                        // Group the options under their Module Group so a long
                        // module list stays scannable in the dropdown.
                        const groups = (course.moduleGroups || []).map(g => ({
                          ...g,
                          modules: course.modules.filter(m => m.group === g.id),
                        }));
                        const ungrouped = course.modules.filter(m => !m.group);
                        const opt = (m) => (
                          <option key={m.id} value={m.id}>
                            M{m.n} · {locText(m.title)} · {m.layouts.length} layout{m.layouts.length === 1 ? '' : 's'}
                          </option>
                        );
                        return <>
                          {groups.filter(g => g.modules.length).map(g => (
                            <optgroup key={g.id} label={locText(g.title)}>{g.modules.map(opt)}</optgroup>
                          ))}
                          {ungrouped.length > 0 && (
                            <optgroup label="(Ungrouped)">{ungrouped.map(opt)}</optgroup>
                          )}
                        </>;
                      })()}
                    </select>
                  </div>
                }
              </SeedOption>
            </div>
          </AMField>
        </div>

        {/* ── Footer ───────────────────────────────────────────────── */}
        <div style={{
          padding: '12px 20px',
          borderTop: '1px solid var(--border)',
          background: 'var(--surface-inset)',
          display: 'flex', alignItems: 'center', gap: 10
        }}>
          <span style={{ fontSize: 11, color: 'var(--text-faint)' }}>
            {seedMode === 'duplicate'
              ? <><I.Info size={11} /> Source bindings are not carried over.</>
              : null}
          </span>
          <div style={{ flex: 1 }} />
          <button className="btn sm" onClick={onCancel}>
            Cancel
          </button>
          <button className="btn sm primary" onClick={handleSubmit}
          disabled={!canSubmit}
          style={{ minWidth: 130, justifyContent: 'center' }}>
            {seedMode === 'duplicate' ?
            <><I.Copy size={12} />{primaryLabel}</> :
            <><I.Plus size={12} />{primaryLabel}</>}
          </button>
        </div>
      </div>
    </div>);

}

// ─── Helpers ───────────────────────────────────────────────────────────────

function AMField({ label, subtitle, required, children }) {
  return (
    <label style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
      <span style={{ fontSize: 11.5, fontWeight: 600, color: 'var(--text-muted)',
        letterSpacing: '.02em' }}>
        {label}{required && <span style={{ color: 'var(--accent)', marginLeft: 2 }}>*</span>}
      </span>
      {children}
      {subtitle &&
      <span style={{ fontSize: 11, color: 'var(--text-faint)', marginTop: 2 }}>{subtitle}</span>
      }
    </label>);

}

function GroupChip({ icon, label, hint, selected, accent, onClick }) {
  // The "+ New group" chip is a primary-style affordance: it always carries
  // the accent tint so the create action stands out from the existing groups.
  const bg =
    accent ? 'var(--accent-bg)' :
    selected ? 'var(--accent-bg)' : 'var(--surface)';
  const borderColor =
    accent ? 'var(--accent-border)' :
    selected ? 'var(--accent-border)' : 'var(--border)';
  const color =
    accent ? 'var(--accent-text)' :
    selected ? 'var(--text)' : 'var(--text)';
  const iconColor =
    accent ? 'var(--accent)' :
    selected ? 'var(--text-muted)' : 'var(--text-muted)';
  return (
    <button onClick={onClick}
    style={{
      display: 'inline-flex', alignItems: 'center', gap: 6,
      padding: '6px 10px',
      background: bg,
      border: '1px solid',
      borderColor,
      color,
      borderRadius: 'var(--radius)',
      boxShadow: selected ? '0 0 0 1px ' + (accent ? 'var(--accent)' : 'var(--accent-border)') : 'none',
      fontFamily: 'inherit', fontSize: 12.5, fontWeight: 500,
      cursor: 'default',
      transition: 'border-color 120ms, background 120ms'
    }}>
      <span style={{ display: 'inline-flex', color: iconColor }}>{icon}</span>
      <span>{label}</span>
      {hint && <span style={{ fontSize: 10.5, color: 'var(--text-faint)',
        fontFamily: 'var(--font-mono)' }}>{hint}</span>}
    </button>);

}

// `disabled` renders the row as an honest, non-selectable preview: the click
// handler is dropped (so no keyboard or mouse path can select it), the radio
// dot is greyed, and the label is muted. Use it with a <PreviewNote> child that
// says WHY — never leave a control that looks selectable but silently is not.
function SeedOption({ selected, onSelect, icon, title, desc, ai, disabled, seedKey, children }) {
  const hasDesc = desc && String(desc).trim().length > 0;
  return (
    <div onClick={disabled ? undefined : onSelect}
    // `data-seed` is a stable hook so a test can select THIS row rather than
    // guessing at the smallest element containing the label — an earlier version
    // of the harness matched an ancestor and its assertion could not fail.
    data-seed={seedKey}
    role="radio"
    aria-checked={selected ? 'true' : 'false'}
    aria-disabled={disabled ? 'true' : undefined}
    style={{
      padding: '10px 14px',
      background: disabled ? 'var(--surface-inset)' : 'var(--surface)',
      border: '1px solid',
      borderColor: selected ? 'var(--accent-border)' : 'var(--border)',
      boxShadow: selected ? '0 0 0 1px var(--accent-border)' : 'none',
      borderRadius: 'var(--radius-md)',
      cursor: 'default',
      opacity: disabled ? 0.72 : 1,
      transition: 'border-color 120ms, box-shadow 120ms'
    }}
    onMouseOver={(e) => {if (!selected && !disabled) e.currentTarget.style.borderColor = 'var(--text-faint)';}}
    onMouseOut={(e) => {if (!selected && !disabled) e.currentTarget.style.borderColor = 'var(--border)';}}>
      <div style={{ display: 'grid',
        gridTemplateColumns: 'auto auto 1fr', gap: 10,
        alignItems: 'center' }}>
        <span style={{
          width: 16, height: 16, borderRadius: '50%',
          border: '1.5px solid', borderColor: selected ? 'var(--accent)' : 'var(--border-strong)',
          background: selected ? 'var(--accent)' : 'transparent',
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
          flexShrink: 0,
        }}>
          {selected && <span style={{ width: 6, height: 6, borderRadius: '50%',
            background: 'var(--text-on-accent)' }} />}
        </span>
        <span style={{
          width: 26, height: 26, borderRadius: 7,
          background: ai ?
          'color-mix(in oklab, var(--ai) 14%, transparent)' :
          'var(--surface-inset)',
          color: ai ? 'var(--ai)' : 'var(--text-muted)',
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
          flexShrink: 0,
        }}>{icon}</span>
        <div style={{ minWidth: 0, display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ fontSize: 13.5, fontWeight: 600,
            color: disabled ? 'var(--text-muted)' : 'var(--text)' }}>{title}</span>
          {ai && <span className="pill ai" style={{ fontSize: 10 }}>AI</span>}
          {hasDesc && (
            <span style={{ fontSize: 12, color: 'var(--text-muted)',
              lineHeight: 1.4, marginLeft: 4 }}>· {desc}</span>
          )}
        </div>
      </div>
      {children &&
      <div onClick={(e) => e.stopPropagation()} style={{ paddingLeft: 36 }}>
          {children}
        </div>
      }
    </div>);

}

Object.assign(window, { AddModuleDialog });
