// Workspace — interactive shells: modal, popover, NewTaskModal, NewDocModal,
// StatusPopover, AssigneePopover, PhasePopover. Loaded after primitives.jsx
// and before layout.jsx so every other module can read these globals.

// ── Modal shell ─────────────────────────────────────────────────────
function Modal({ title, onClose, width = 480, footer, children }) {
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose && onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);
  return (
    <>
      <div onClick={onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(0,0,0,.55)', zIndex: 100,
        animation: 'fade-in 120ms', backdropFilter: 'blur(2px)',
      }} />
      <div role="dialog" aria-modal="true" style={{
        position: 'fixed', top: '12%', left: '50%', transform: 'translateX(-50%)',
        width, maxWidth: 'calc(100vw - 32px)', maxHeight: '76vh',
        background: 'var(--bg-1)', border: '1px solid var(--border-strong)',
        borderRadius: 'var(--r-lg)', boxShadow: '0 24px 60px rgba(0,0,0,.6)',
        zIndex: 110, display: 'flex', flexDirection: 'column', overflow: 'hidden',
      }}>
        <div style={{
          padding: '14px 18px', borderBottom: '1px solid var(--border)',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        }}>
          <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--text-0)' }}>{title}</div>
          <button className="btn-reset" onClick={onClose}
            style={{ width: 26, height: 26, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 5, color: 'var(--text-2)' }}>
            <Icon name="x" size={15} />
          </button>
        </div>
        <div style={{ flex: 1, overflowY: 'auto', padding: '16px 18px' }}>{children}</div>
        {footer && (
          <div style={{ padding: '12px 18px', borderTop: '1px solid var(--border)', display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
            {footer}
          </div>
        )}
      </div>
    </>
  );
}

// ── Popover ─────────────────────────────────────────────────────────
// `align` is 'left' | 'right' relative to the anchor.
//
// Outside-click handling: the popover listens to mousedown on document and
// closes when the click is outside BOTH the anchor and the popover itself.
// Without the popover-self check, a mousedown on any option inside the
// popover fires this handler before the option's own onClick runs (mousedown
// happens first in the DOM event sequence), so onClose() races onPick() and
// the popover snaps shut "without doing anything".
//
// Stacking-context note: the popover renders via createPortal to document.body
// so it escapes the Modal's transform-based containing block. Modal uses
// `transform: translateX(-50%)`, which makes `position: fixed` children
// resolve relative to the modal instead of the viewport — without the portal
// the getBoundingClientRect-derived coords land off, and z-index is trapped
// under the modal's own backdrop. That's the "Estado / Prioridad / Fase /
// Asignado a no funcionan" bug in NewTaskModal.
function Popover({ anchorRef, onClose, width = 220, align = 'left', children }) {
  const [pos, setPos] = React.useState(null);
  const popoverRef = React.useRef(null);
  React.useLayoutEffect(() => {
    if (!anchorRef?.current) return;
    const r = anchorRef.current.getBoundingClientRect();
    const top = r.bottom + 6;
    const left = align === 'right' ? Math.max(8, r.right - width) : r.left;
    setPos({ top, left });
  }, [anchorRef, align, width]);
  React.useEffect(() => {
    const onDoc = (e) => {
      if (anchorRef?.current && anchorRef.current.contains(e.target)) return;
      if (popoverRef.current && popoverRef.current.contains(e.target)) return;
      onClose && onClose();
    };
    const onKey = (e) => { if (e.key === 'Escape') onClose && onClose(); };
    document.addEventListener('mousedown', onDoc);
    window.addEventListener('keydown', onKey);
    return () => { document.removeEventListener('mousedown', onDoc); window.removeEventListener('keydown', onKey); };
  }, [anchorRef, onClose]);
  if (!pos) return null;
  const node = (
    <div ref={popoverRef} style={{
      position: 'fixed', top: pos.top, left: pos.left, width, zIndex: 9999,
      background: 'var(--bg-1)', border: '1px solid var(--border-strong)',
      borderRadius: 'var(--r-md)', boxShadow: '0 12px 32px rgba(0,0,0,.5)',
      padding: 4, animation: 'fade-in 100ms',
    }}>
      {children}
    </div>
  );
  return ReactDOM.createPortal(node, document.body);
}

// Single-row item used inside Popover.
function PopItem({ icon, color, label, sub, active, onClick, kbd }) {
  const [hover, setHover] = React.useState(false);
  return (
    <button className="btn-reset" onClick={onClick}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{
        display: 'flex', alignItems: 'center', gap: 10, width: '100%',
        padding: '7px 10px', borderRadius: 6, textAlign: 'left',
        background: active ? 'var(--accent-dim)' : (hover ? 'var(--bg-2)' : 'transparent'),
        color: active ? 'var(--accent)' : 'var(--text-0)',
      }}>
      {icon && <Icon name={icon} size={13} color={color || (active ? 'var(--accent)' : 'var(--text-2)')} />}
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 12.5, fontWeight: active ? 500 : 400 }}>{label}</div>
        {sub && <div style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 1 }}>{sub}</div>}
      </div>
      {active && <Icon name="check" size={12} color="var(--accent)" />}
      {kbd && <span className="mono" style={{ fontSize: 10.5, color: 'var(--text-3)' }}>{kbd}</span>}
    </button>
  );
}

// ── StatusPopover ────────────────────────────────────────────────────
const TASK_STATUS_OPTIONS = [
  { id: 'backlog',  label: 'Backlog' },
  { id: 'ready',    label: 'Listo para empezar' },
  { id: 'progress', label: 'En curso' },
  { id: 'review',   label: 'En revisión' },
  { id: 'blocked',  label: 'Bloqueado' },
  { id: 'closed',   label: 'Cerrado' },
];
function StatusPopover({ value, onPick, anchorRef, onClose }) {
  return (
    <Popover anchorRef={anchorRef} onClose={onClose} width={220}>
      {TASK_STATUS_OPTIONS.map((s) => {
        const m = STATUS_META[s.id];
        return (
          <PopItem key={s.id}
            icon="circle" color={m.color}
            label={m.label}
            active={value === s.id}
            onClick={() => { onPick(s.id); onClose(); }} />
        );
      })}
    </Popover>
  );
}

// ── AssigneePopover ──────────────────────────────────────────────────
//
// Composition rules:
//   - Humans: workspace members from `window.MEMBER_IDS` (loaded by
//     loadActorsCatalog from /api/workspace/members). Falls back to
//     ACTOR_LIST when empty (legacy single-tenant deploy).
//   - Agents: the seeded `code` (Claude Code) and `claude` (Claude
//     Strategic Advisor) are always appended so users can delegate tasks
//     to the AI agents from any workspace, not just the legacy founder
//     workspace.
const _AGENT_ASSIGNEES = ['code', 'claude'];
function AssigneePopover({ value, onPick, anchorRef, onClose, allowUnassign = true }) {
  const humans = (window.MEMBER_IDS && window.MEMBER_IDS.length > 0)
    ? window.MEMBER_IDS
    : ACTOR_LIST.filter((id) => !_AGENT_ASSIGNEES.includes(id));
  return (
    <Popover anchorRef={anchorRef} onClose={onClose} width={260}>
      {humans.map((id) => (
        <PopItem key={id}
          icon="user" color={ACTORS[id].color}
          label={ACTORS[id].name}
          sub={ACTORS[id].role || ACTORS[id].desc}
          active={value === id}
          onClick={() => { onPick(id); onClose(); }} />
      ))}
      {humans.length > 0 && _AGENT_ASSIGNEES.length > 0 && (
        <div style={{ height: 1, background: 'var(--border)', margin: '4px 6px' }} />
      )}
      {_AGENT_ASSIGNEES.map((id) => (
        <PopItem key={id}
          icon="bot" color={ACTORS[id].color}
          label={ACTORS[id].name}
          sub={ACTORS[id].role || ACTORS[id].desc}
          active={value === id}
          onClick={() => { onPick(id); onClose(); }} />
      ))}
      {allowUnassign && (
        <>
          <div style={{ height: 1, background: 'var(--border)', margin: '4px 6px' }} />
          <PopItem icon="user-x" label="Sin asignar"
            active={!value}
            onClick={() => { onPick(null); onClose(); }} />
        </>
      )}
    </Popover>
  );
}

// ── PhasePopover ─────────────────────────────────────────────────────
function PhasePopover({ value, onPick, anchorRef, onClose }) {
  const phases = window.PHASES || [];
  return (
    <Popover anchorRef={anchorRef} onClose={onClose} width={260}>
      {phases.length === 0 && (
        <div style={{ padding: '10px 12px', fontSize: 11.5, color: 'var(--text-3)', fontStyle: 'italic' }}>
          No hay fases. Créalas con MCP <code>create_phase</code> o desde Tracker / Fases.
        </div>
      )}
      {phases.map((p) => (
        <PopItem key={p.id}
          icon="layers" color={p.status === 'active' ? 'var(--accent)' : p.status === 'done' ? 'var(--success)' : 'var(--text-2)'}
          label={p.name}
          sub={p.id}
          active={value === p.id}
          onClick={() => { onPick(p.id); onClose(); }} />
      ))}
      <div style={{ height: 1, background: 'var(--border)', margin: '4px 6px' }} />
      <PopItem icon="x" label="Sin fase"
        active={!value}
        onClick={() => { onPick(null); onClose(); }} />
    </Popover>
  );
}

// ── PriorityPopover ──────────────────────────────────────────────────
const TASK_PRIORITY_OPTIONS = [
  { id: 'P0', label: 'P0 · Crítica' },
  { id: 'P1', label: 'P1 · Alta' },
  { id: 'P2', label: 'P2 · Normal' },
  { id: 'P3', label: 'P3 · Baja' },
];
function PriorityPopover({ value, onPick, anchorRef, onClose }) {
  return (
    <Popover anchorRef={anchorRef} onClose={onClose} width={200}>
      {TASK_PRIORITY_OPTIONS.map((p) => {
        const m = PRIORITY_META[p.id];
        return (
          <PopItem key={p.id}
            icon="flag" color={m?.color || 'var(--text-2)'}
            label={p.label}
            active={value === p.id}
            onClick={() => { onPick(p.id); onClose(); }} />
        );
      })}
    </Popover>
  );
}

// ── FolderPopover ────────────────────────────────────────────────────
// `value` and `onPick` are folder *slugs* (per-project, e.g. "specs"). The
// popover shows the display name + the slug separately, since after rename
// they can diverge.
function FolderPopover({ value, onPick, anchorRef, onClose }) {
  const folders = window.VAULT_TREE || [];
  return (
    <Popover anchorRef={anchorRef} onClose={onClose} width={260}>
      {folders.map((f) => {
        const slug = f.slug || f.id;
        const name = f.name || slug;
        return (
          <PopItem key={f.id}
            icon={f.icon || 'folder'}
            label={
              <span style={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
                <span>{name}</span>
                {name !== slug && (
                  <span className="mono" style={{ fontSize: 10.5, color: 'var(--text-3)' }}>/{slug}</span>
                )}
              </span>
            }
            active={value === slug}
            onClick={() => { onPick(slug); onClose(); }} />
        );
      })}
    </Popover>
  );
}

// ── Form bits ───────────────────────────────────────────────────────
function FormRow({ label, hint, children }) {
  return (
    <div style={{ marginBottom: 14 }}>
      <div style={{ fontSize: 11.5, fontWeight: 500, color: 'var(--text-2)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: 0.5 }}>{label}</div>
      {children}
      {hint && <div style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 4 }}>{hint}</div>}
    </div>
  );
}
function PickerButton({ icon, color, label, placeholder, onClick, anchorRef }) {
  return (
    <button ref={anchorRef} className="btn-reset" onClick={onClick}
      style={{
        display: 'flex', alignItems: 'center', gap: 8,
        width: '100%', height: 32, padding: '0 10px',
        background: 'var(--bg-0)', border: '1px solid var(--border)',
        borderRadius: 6, color: label ? 'var(--text-0)' : 'var(--text-3)',
        fontSize: 12.5, textAlign: 'left',
      }}>
      {icon && <Icon name={icon} size={13} color={color || 'var(--text-2)'} />}
      <span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{label || placeholder}</span>
      <Icon name="chevron-down" size={12} color="var(--text-3)" />
    </button>
  );
}

// ── NewTaskModal ─────────────────────────────────────────────────────
function NewTaskModal({ onClose, onCreated, presetStatus, presetPhase, presetAssignee }) {
  const [title, setTitle] = React.useState('');
  const [description, setDescription] = React.useState('');
  const [status, setStatus] = React.useState(presetStatus || 'backlog');
  const [priority, setPriority] = React.useState('P2');
  const [phaseId, setPhaseId] = React.useState(presetPhase || null);
  const [assigneeId, setAssigneeId] = React.useState(presetAssignee || (window.ME?.id ?? null));
  const [submitting, setSubmitting] = React.useState(false);
  const [error, setError] = React.useState(null);

  const statusRef = React.useRef(null);
  const priorityRef = React.useRef(null);
  const phaseRef = React.useRef(null);
  const assigneeRef = React.useRef(null);
  const [open, setOpen] = React.useState(null); // 'status' | 'priority' | 'phase' | 'assignee' | null

  const submit = async () => {
    if (!title.trim()) { setError('El título es obligatorio.'); return; }
    if (!window.CURRENT_PROJECT_ID) {
      setError('Selecciona un proyecto antes de crear la tarea.');
      return;
    }
    setSubmitting(true);
    setError(null);
    try {
      const t = await window.api('POST', '/api/tasks', {
        title: title.trim(),
        description,
        status,
        priority,
        phaseId,
        assigneeId,
        // Send projectId in the body too. The api() wrapper already includes
        // it as X-Project-Id, but defending against header loss (proxy, etc.)
        // is cheap, and the backend reads body.projectId first anyway.
        projectId: window.CURRENT_PROJECT_ID,
      });
      await window.loadAll();
      onCreated && onCreated(t.id);
      onClose();
    } catch (e) {
      setError(e.message || 'Error creando la tarea.');
      setSubmitting(false);
    }
  };

  const sm = STATUS_META[status];
  const pm = PRIORITY_META[priority];
  const phaseObj = (window.PHASES || []).find((p) => p.id === phaseId);

  return (
    <Modal title="Nueva tarea" onClose={onClose} width={520}
      footer={<>
        <Button variant="ghost" onClick={onClose}>Cancelar</Button>
        <Button variant="primary" onClick={submit} disabled={submitting || !title.trim()}>
          {submitting ? 'Creando…' : 'Crear tarea'}
        </Button>
      </>}>
      <FormRow label="Título">
        <input autoFocus value={title} onChange={(e) => setTitle(e.target.value)}
          placeholder="¿Qué hay que hacer?"
          style={{
            width: '100%', height: 36, padding: '0 12px',
            background: 'var(--bg-0)', border: '1px solid var(--border)',
            borderRadius: 6, color: 'var(--text-0)', fontSize: 14, outline: 'none',
          }} />
      </FormRow>
      <FormRow label="Descripción (Markdown)">
        <textarea rows={4} value={description} onChange={(e) => setDescription(e.target.value)}
          style={{
            width: '100%', padding: '8px 12px', resize: 'vertical',
            background: 'var(--bg-0)', border: '1px solid var(--border)',
            borderRadius: 6, color: 'var(--text-0)', fontSize: 13,
            fontFamily: 'inherit', outline: 'none',
          }} />
      </FormRow>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 12 }}>
        <FormRow label="Estado">
          <PickerButton icon="circle" color={sm.color} label={sm.label}
            onClick={() => setOpen('status')} anchorRef={statusRef} />
        </FormRow>
        <FormRow label="Prioridad">
          <PickerButton icon="flag" color={pm?.color} label={priority}
            onClick={() => setOpen('priority')} anchorRef={priorityRef} />
        </FormRow>
        <FormRow label="Fase">
          <PickerButton icon="layers" label={phaseObj ? `${phaseObj.id} · ${phaseObj.name}` : null}
            placeholder="Sin fase"
            onClick={() => setOpen('phase')} anchorRef={phaseRef} />
        </FormRow>
        <FormRow label="Asignado a">
          <PickerButton icon="user" color={assigneeId ? ACTORS[assigneeId].color : null}
            label={assigneeId ? ACTORS[assigneeId].name : null}
            placeholder="Sin asignar"
            onClick={() => setOpen('assignee')} anchorRef={assigneeRef} />
        </FormRow>
      </div>
      {error && <div style={{ color: 'var(--danger)', fontSize: 12.5, marginTop: 6 }}>{error}</div>}

      {open === 'status' && <StatusPopover value={status} onPick={setStatus} anchorRef={statusRef} onClose={() => setOpen(null)} />}
      {open === 'priority' && <PriorityPopover value={priority} onPick={setPriority} anchorRef={priorityRef} onClose={() => setOpen(null)} />}
      {open === 'phase' && <PhasePopover value={phaseId} onPick={setPhaseId} anchorRef={phaseRef} onClose={() => setOpen(null)} />}
      {open === 'assignee' && <AssigneePopover value={assigneeId} onPick={setAssigneeId} anchorRef={assigneeRef} onClose={() => setOpen(null)} />}
    </Modal>
  );
}

// ── NewDocModal ──────────────────────────────────────────────────────
function NewDocModal({ onClose, onCreated, presetFolder }) {
  // `folderId` here is actually the per-project slug (e.g. "specs"). Backend
  // accepts either UUID or slug for the `folderId` field on POST /api/docs.
  const _firstFolder = window.VAULT_TREE?.[0];
  const [folderId, setFolderId] = React.useState(
    presetFolder || (_firstFolder ? (_firstFolder.slug || _firstFolder.id) : 'inbox'),
  );
  const [title, setTitle] = React.useState('');
  const [slug, setSlug] = React.useState('');
  const [bodyMd, setBodyMd] = React.useState('');
  const [submitting, setSubmitting] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [touchedSlug, setTouchedSlug] = React.useState(false);
  const folderRef = React.useRef(null);
  const [openFolder, setOpenFolder] = React.useState(false);

  React.useEffect(() => {
    if (touchedSlug) return;
    const auto = title.toLowerCase()
      .normalize('NFD').replace(/[̀-ͯ]/g, '')
      .replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '').slice(0, 60);
    setSlug(auto ? `${auto}.md` : '');
  }, [title, touchedSlug]);

  const submit = async () => {
    if (!title.trim() || !slug.trim()) { setError('Título y slug son obligatorios.'); return; }
    setSubmitting(true);
    setError(null);
    try {
      const doc = await window.api('POST', '/api/docs', {
        folderId, slug: slug.trim(), title: title.trim(), bodyMd,
      });
      await window.loadAll();
      onCreated && onCreated(`${folderId}/${doc.slug}`);
      onClose();
    } catch (e) {
      setError(e.message || 'Error creando el documento.');
      setSubmitting(false);
    }
  };

  return (
    <Modal title="Nuevo documento" onClose={onClose} width={560}
      footer={<>
        <Button variant="ghost" onClick={onClose}>Cancelar</Button>
        <Button variant="primary" onClick={submit} disabled={submitting || !title.trim() || !slug.trim()}>
          {submitting ? 'Creando…' : 'Crear documento'}
        </Button>
      </>}>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
        <FormRow label="Carpeta">
          <PickerButton icon="folder" label={`/${folderId}`}
            onClick={() => setOpenFolder(true)} anchorRef={folderRef} />
        </FormRow>
        <FormRow label="Slug (filename)">
          <input value={slug} onChange={(e) => { setTouchedSlug(true); setSlug(e.target.value); }}
            placeholder="ejemplo.md"
            style={{
              width: '100%', height: 32, padding: '0 10px',
              background: 'var(--bg-0)', border: '1px solid var(--border)',
              borderRadius: 6, color: 'var(--text-0)', fontSize: 12.5,
              fontFamily: 'var(--font-mono)', outline: 'none',
            }} />
        </FormRow>
      </div>
      <FormRow label="Título">
        <input autoFocus value={title} onChange={(e) => setTitle(e.target.value)}
          placeholder="Título del documento"
          style={{
            width: '100%', height: 36, padding: '0 12px',
            background: 'var(--bg-0)', border: '1px solid var(--border)',
            borderRadius: 6, color: 'var(--text-0)', fontSize: 14, outline: 'none',
          }} />
      </FormRow>
      <FormRow label="Contenido inicial (Markdown, opcional)">
        <textarea rows={6} value={bodyMd} onChange={(e) => setBodyMd(e.target.value)}
          placeholder="# Título&#10;&#10;Cuerpo del documento…"
          style={{
            width: '100%', padding: '8px 12px', resize: 'vertical',
            background: 'var(--bg-0)', border: '1px solid var(--border)',
            borderRadius: 6, color: 'var(--text-0)', fontSize: 13,
            fontFamily: 'var(--font-mono)', outline: 'none',
          }} />
      </FormRow>
      {error && <div style={{ color: 'var(--danger)', fontSize: 12.5, marginTop: 6 }}>{error}</div>}

      {openFolder && <FolderPopover value={folderId} onPick={setFolderId} anchorRef={folderRef} onClose={() => setOpenFolder(false)} />}
    </Modal>
  );
}

// ── NewPhaseModal ────────────────────────────────────────────────────
function NewPhaseModal({ onClose, onCreated, initial }) {
  const isEdit = !!initial;
  const [id, setId] = React.useState(initial?.id || '');
  const [name, setName] = React.useState(initial?.name || '');
  const [description, setDescription] = React.useState(initial?.description || '');
  const [status, setStatus] = React.useState(initial?.status || 'queued');
  const [startsAt, setStartsAt] = React.useState(initial?.startsAt || '');
  const [dueAt, setDueAt] = React.useState(initial?.dueAt || '');
  const [ownerId, setOwnerId] = React.useState(initial?.ownerId || null);
  const [tagsRaw, setTagsRaw] = React.useState((initial?.tags || []).join(', '));
  const [submitting, setSubmitting] = React.useState(false);
  const [error, setError] = React.useState(null);
  const ownerRef = React.useRef(null);
  const [openOwner, setOpenOwner] = React.useState(false);

  const submit = async () => {
    if (!id.trim() || !name.trim()) { setError('Id y nombre son obligatorios.'); return; }
    if (startsAt && dueAt && startsAt > dueAt) { setError('La fecha de inicio debe ser anterior a la fecha objetivo.'); return; }
    setSubmitting(true);
    setError(null);
    const tags = tagsRaw.split(/[,\s]+/).map(s => s.trim()).filter(Boolean);
    try {
      const body = {
        name: name.trim(),
        description,
        status,
        startsAt: startsAt || null,
        dueAt: dueAt || null,
        ownerId,
        tags,
      };
      let p;
      if (isEdit) {
        p = await window.api('PATCH', `/api/phases/${encodeURIComponent(initial.id)}`, body);
      } else {
        p = await window.api('POST', '/api/phases', { id: id.trim(), ...body });
      }
      await window.loadAll();
      onCreated && onCreated(p.id);
      onClose();
    } catch (e) {
      setError(e.message || 'Error guardando la fase.');
      setSubmitting(false);
    }
  };

  return (
    <Modal title={isEdit ? `Editar fase ${initial.id}` : 'Nueva fase'} onClose={onClose} width={620}
      footer={<>
        <Button variant="ghost" onClick={onClose}>Cancelar</Button>
        <Button variant="primary" onClick={submit} disabled={submitting || !id.trim() || !name.trim()}>
          {submitting ? 'Guardando…' : isEdit ? 'Guardar cambios' : 'Crear fase'}
        </Button>
      </>}>
      <div style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: 12 }}>
        <FormRow label="Id">
          <input value={id} onChange={(e) => setId(e.target.value)} placeholder="S0, II-7"
            disabled={isEdit}
            style={{
              width: '100%', height: 32, padding: '0 10px',
              background: 'var(--bg-0)', border: '1px solid var(--border)',
              borderRadius: 6, color: isEdit ? 'var(--text-3)' : 'var(--text-0)', fontSize: 13, fontFamily: 'var(--font-mono)', outline: 'none',
            }} />
        </FormRow>
        <FormRow label="Nombre">
          <input autoFocus value={name} onChange={(e) => setName(e.target.value)} placeholder="Lexer / Parser"
            style={{
              width: '100%', height: 32, padding: '0 10px',
              background: 'var(--bg-0)', border: '1px solid var(--border)',
              borderRadius: 6, color: 'var(--text-0)', fontSize: 13, outline: 'none',
            }} />
        </FormRow>
      </div>
      <FormRow label="Descripción (Markdown)" hint="Contexto, objetivos, scope de la fase">
        <textarea value={description} onChange={(e) => setDescription(e.target.value)} rows={5}
          placeholder="## Objetivos&#10;- &#10;&#10;## Scope&#10;- "
          style={{
            width: '100%', padding: '8px 12px', resize: 'vertical',
            background: 'var(--bg-0)', border: '1px solid var(--border)',
            borderRadius: 6, color: 'var(--text-0)', fontSize: 13,
            fontFamily: 'var(--font-mono)', outline: 'none',
          }} />
      </FormRow>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12 }}>
        <FormRow label="Estado">
          <select value={status} onChange={(e) => setStatus(e.target.value)}
            style={{
              width: '100%', height: 32, padding: '0 8px',
              background: 'var(--bg-0)', border: '1px solid var(--border)',
              borderRadius: 6, color: 'var(--text-0)', fontSize: 12.5, outline: 'none',
            }}>
            <option value="queued">Pendiente</option>
            <option value="active">Activa</option>
            <option value="done">Cerrada</option>
          </select>
        </FormRow>
        <FormRow label="Responsable">
          <PickerButton icon="user" color={ownerId ? ACTORS[ownerId].color : null}
            label={ownerId ? ACTORS[ownerId].name : null}
            placeholder="Sin asignar"
            onClick={() => setOpenOwner(true)} anchorRef={ownerRef} />
        </FormRow>
        <FormRow label="Tags" hint="Separados por coma">
          <input value={tagsRaw} onChange={(e) => setTagsRaw(e.target.value)} placeholder="planning, infra"
            style={{
              width: '100%', height: 32, padding: '0 10px',
              background: 'var(--bg-0)', border: '1px solid var(--border)',
              borderRadius: 6, color: 'var(--text-0)', fontSize: 12.5, outline: 'none',
            }} />
        </FormRow>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
        <FormRow label="Inicio (opcional)" hint="Fecha de inicio del rango">
          <input type="date" value={startsAt} onChange={(e) => setStartsAt(e.target.value)}
            style={{
              width: '100%', height: 32, padding: '0 10px',
              background: 'var(--bg-0)', border: '1px solid var(--border)',
              borderRadius: 6, color: 'var(--text-0)', fontSize: 12.5, outline: 'none',
            }} />
        </FormRow>
        <FormRow label="Fin / objetivo (opcional)" hint="Fecha objetivo o fin del rango">
          <input type="date" value={dueAt} onChange={(e) => setDueAt(e.target.value)}
            style={{
              width: '100%', height: 32, padding: '0 10px',
              background: 'var(--bg-0)', border: '1px solid var(--border)',
              borderRadius: 6, color: 'var(--text-0)', fontSize: 12.5, outline: 'none',
            }} />
        </FormRow>
      </div>
      {error && <div style={{ color: 'var(--danger)', fontSize: 12.5, marginTop: 6 }}>{error}</div>}

      {openOwner && <AssigneePopover value={ownerId} onPick={setOwnerId} anchorRef={ownerRef} onClose={() => setOpenOwner(false)} />}
    </Modal>
  );
}

// ── NewFolderModal / EditFolderModal / DeleteFolderModal ──────────────
//
// Folders are per-project. The slug is the URL identifier (e.g. /specs);
// the name is the display label shown in the sidebar. Slug auto-derives
// from name unless the user types one manually.

const _LUCIDE_FOLDER_ICON_SUGGESTIONS = [
  'folder', 'file-cog', 'message-square-text', 'gavel', 'flask-conical',
  'briefcase', 'user-cog', 'inbox', 'rocket', 'book-open', 'lightbulb',
  'wrench', 'database', 'beaker', 'graduation-cap', 'pin',
];

function _slugify(name) {
  return (name || '').toLowerCase()
    .normalize('NFD').replace(/[̀-ͯ]/g, '')
    .replace(/[^a-z0-9-]+/g, '-').replace(/(^-|-$)/g, '').slice(0, 48);
}

function NewFolderModal({ onClose }) {
  const [name, setName] = React.useState('');
  const [slug, setSlug] = React.useState('');
  const [icon, setIcon] = React.useState('folder');
  const [touchedSlug, setTouchedSlug] = React.useState(false);
  const [submitting, setSubmitting] = React.useState(false);
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
    if (touchedSlug) return;
    setSlug(_slugify(name));
  }, [name, touchedSlug]);

  const submit = async () => {
    const cleanName = name.trim();
    const cleanSlug = (slug || _slugify(cleanName)).trim();
    if (!cleanName || !cleanSlug) { setError('Nombre y slug son obligatorios.'); return; }
    setSubmitting(true);
    setError(null);
    try {
      await window.createFolder({ name: cleanName, slug: cleanSlug, icon });
      onClose();
    } catch (e) {
      setError(e.message || 'No se pudo crear la carpeta.');
      setSubmitting(false);
    }
  };

  return (
    <Modal title="Nueva carpeta" onClose={onClose} width={460}
      footer={<>
        <Button variant="ghost" onClick={onClose}>Cancelar</Button>
        <Button variant="primary" onClick={submit} disabled={submitting || !name.trim()}>
          {submitting ? 'Creando…' : 'Crear carpeta'}
        </Button>
      </>}>
      <FormRow label="Nombre">
        <input autoFocus value={name} onChange={(e) => setName(e.target.value)}
          placeholder="Por ejemplo: Marketing"
          style={{
            width: '100%', height: 36, padding: '0 12px',
            background: 'var(--bg-0)', border: '1px solid var(--border)',
            borderRadius: 6, color: 'var(--text-0)', fontSize: 14, outline: 'none',
          }} />
      </FormRow>
      <FormRow label="Slug (URL)" hint="Solo minúsculas, dígitos y guiones. Se autogenera del nombre.">
        <input value={slug} onChange={(e) => { setTouchedSlug(true); setSlug(e.target.value); }}
          placeholder="marketing"
          style={{
            width: '100%', height: 32, padding: '0 10px',
            background: 'var(--bg-0)', border: '1px solid var(--border)',
            borderRadius: 6, color: 'var(--text-0)', fontSize: 12.5,
            fontFamily: 'var(--font-mono)', outline: 'none',
          }} />
      </FormRow>
      <FormRow label="Ícono" hint="Nombre de un ícono Lucide.">
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
          {_LUCIDE_FOLDER_ICON_SUGGESTIONS.map((nm) => (
            <button key={nm} className="btn-reset" onClick={() => setIcon(nm)}
              style={{
                width: 30, height: 30, display: 'flex', alignItems: 'center', justifyContent: 'center',
                borderRadius: 6,
                border: '1px solid ' + (icon === nm ? 'var(--accent)' : 'var(--border)'),
                background: icon === nm ? 'var(--accent-dim)' : 'var(--bg-0)',
                color: icon === nm ? 'var(--accent)' : 'var(--text-1)',
              }}
              title={nm}>
              <Icon name={nm} size={14} />
            </button>
          ))}
        </div>
      </FormRow>
      <FormRow label="">
        <input value={icon} onChange={(e) => setIcon(e.target.value)}
          placeholder="otro ícono Lucide"
          style={{
            width: '100%', height: 30, padding: '0 10px',
            background: 'var(--bg-0)', border: '1px solid var(--border)',
            borderRadius: 6, color: 'var(--text-0)', fontSize: 12.5,
            fontFamily: 'var(--font-mono)', outline: 'none',
          }} />
      </FormRow>
      {error && <div style={{ color: 'var(--danger)', fontSize: 12.5, marginTop: 6 }}>{error}</div>}
    </Modal>
  );
}

function EditFolderModal({ folder, onClose }) {
  const initialSlug = folder.slug || folder.id;
  const [name, setName] = React.useState(folder.name || initialSlug);
  const [slug, setSlug] = React.useState(initialSlug);
  const [icon, setIcon] = React.useState(folder.icon || 'folder');
  const [submitting, setSubmitting] = React.useState(false);
  const [error, setError] = React.useState(null);

  const submit = async () => {
    const cleanName = name.trim();
    const cleanSlug = slug.trim();
    if (!cleanName || !cleanSlug) { setError('Nombre y slug son obligatorios.'); return; }
    setSubmitting(true);
    setError(null);
    try {
      await window.updateFolder(initialSlug, { name: cleanName, slug: cleanSlug, icon });
      onClose();
    } catch (e) {
      setError(e.message || 'No se pudo actualizar la carpeta.');
      setSubmitting(false);
    }
  };

  return (
    <Modal title={`Editar carpeta /${initialSlug}`} onClose={onClose} width={460}
      footer={<>
        <Button variant="ghost" onClick={onClose}>Cancelar</Button>
        <Button variant="primary" onClick={submit} disabled={submitting || !name.trim()}>
          {submitting ? 'Guardando…' : 'Guardar cambios'}
        </Button>
      </>}>
      <FormRow label="Nombre">
        <input autoFocus value={name} onChange={(e) => setName(e.target.value)}
          style={{
            width: '100%', height: 36, padding: '0 12px',
            background: 'var(--bg-0)', border: '1px solid var(--border)',
            borderRadius: 6, color: 'var(--text-0)', fontSize: 14, outline: 'none',
          }} />
      </FormRow>
      <FormRow label="Slug (URL)" hint="Solo minúsculas, dígitos y guiones. Cambiarlo cambia las URLs de los docs en esta carpeta.">
        <input value={slug} onChange={(e) => setSlug(e.target.value)}
          style={{
            width: '100%', height: 32, padding: '0 10px',
            background: 'var(--bg-0)', border: '1px solid var(--border)',
            borderRadius: 6, color: 'var(--text-0)', fontSize: 12.5,
            fontFamily: 'var(--font-mono)', outline: 'none',
          }} />
      </FormRow>
      <FormRow label="Ícono">
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
          {_LUCIDE_FOLDER_ICON_SUGGESTIONS.map((nm) => (
            <button key={nm} className="btn-reset" onClick={() => setIcon(nm)}
              style={{
                width: 30, height: 30, display: 'flex', alignItems: 'center', justifyContent: 'center',
                borderRadius: 6,
                border: '1px solid ' + (icon === nm ? 'var(--accent)' : 'var(--border)'),
                background: icon === nm ? 'var(--accent-dim)' : 'var(--bg-0)',
                color: icon === nm ? 'var(--accent)' : 'var(--text-1)',
              }}
              title={nm}>
              <Icon name={nm} size={14} />
            </button>
          ))}
        </div>
      </FormRow>
      <FormRow label="">
        <input value={icon} onChange={(e) => setIcon(e.target.value)}
          placeholder="otro ícono Lucide"
          style={{
            width: '100%', height: 30, padding: '0 10px',
            background: 'var(--bg-0)', border: '1px solid var(--border)',
            borderRadius: 6, color: 'var(--text-0)', fontSize: 12.5,
            fontFamily: 'var(--font-mono)', outline: 'none',
          }} />
      </FormRow>
      {error && <div style={{ color: 'var(--danger)', fontSize: 12.5, marginTop: 6 }}>{error}</div>}
    </Modal>
  );
}

function DeleteFolderModal({ folder, onClose }) {
  const slug = folder.slug || folder.id;
  const name = folder.name || slug;
  const [submitting, setSubmitting] = React.useState(false);
  const [error, setError] = React.useState(null);

  const submit = async () => {
    setSubmitting(true);
    setError(null);
    try {
      await window.deleteFolder(slug);
      onClose();
    } catch (e) {
      const detail = e?.body?.details;
      const docCount = detail?.docCount ?? 0;
      const upCount = detail?.uploadCount ?? 0;
      if (docCount > 0 || upCount > 0) {
        setError(`Tiene ${docCount} doc(s) y ${upCount} upload(s). Movelos o borralos primero.`);
      } else {
        setError(e.message || 'No se pudo borrar la carpeta.');
      }
      setSubmitting(false);
    }
  };

  return (
    <Modal title={`Borrar carpeta ${name}`} onClose={onClose} width={420}
      footer={<>
        <Button variant="ghost" onClick={onClose}>Cancelar</Button>
        <Button variant="danger" onClick={submit} disabled={submitting}>
          {submitting ? 'Borrando…' : 'Borrar'}
        </Button>
      </>}>
      <div style={{ fontSize: 13, color: 'var(--text-1)', lineHeight: 1.55 }}>
        ¿Borrar la carpeta <strong>{name}</strong> (<span className="mono">/{slug}</span>) de este proyecto?
      </div>
      <div style={{ fontSize: 12, color: 'var(--text-2)', marginTop: 8 }}>
        Solo se permite borrar carpetas vacías. Mueve o borra los documentos y archivos subidos antes de continuar.
      </div>
      {error && <div style={{ color: 'var(--danger)', fontSize: 12.5, marginTop: 12 }}>{error}</div>}
    </Modal>
  );
}

Object.assign(window, {
  Modal, Popover, PopItem,
  StatusPopover, AssigneePopover, PhasePopover, PriorityPopover, FolderPopover,
  PickerButton, FormRow,
  NewTaskModal, NewDocModal, NewPhaseModal,
  NewFolderModal, EditFolderModal, DeleteFolderModal,
  TASK_STATUS_OPTIONS, TASK_PRIORITY_OPTIONS,
});
