// Workspace — Task drawer + Onboarding + Command palette + Edge-states canvas

// ── Task Drawer ─────────────────────────────────────────────────────
function TaskDrawer({ taskId, onClose }) {
  const [task, setTask] = React.useState(null);
  const [historyOpen, setHistoryOpen] = React.useState(false);
  const [showPreview, setShowPreview] = React.useState(false);
  const [draft, setDraft] = React.useState('');
  const [posting, setPosting] = React.useState(false);
  // Refs + popover state declared at top so hook order stays stable across the
  // early-return path while the task is loading.
  const statusRef = React.useRef(null);
  const priorityRef = React.useRef(null);
  const assigneeRef = React.useRef(null);
  const phaseRef = React.useRef(null);
  const [openPop, setOpenPop] = React.useState(null);
  const minsAgo = (iso) => Math.max(0, Math.round((Date.now() - new Date(iso).getTime()) / 60000));

  React.useEffect(() => {
    let cancelled = false;
    window.fetchTask(taskId).then((res) => {
      if (!cancelled) setTask(res);
    }).catch((e) => console.error(e));
    return () => { cancelled = true; };
  }, [taskId]);

  // Refetch on realtime events for this task.
  React.useEffect(() => {
    return window.onRealtime((msg) => {
      if ((msg.type === 'task.changed' || msg.type === 'task.commented') && msg.taskId === taskId) {
        window.fetchTask(taskId).then(setTask).catch(() => {});
      }
    });
  }, [taskId]);

  if (!task) {
    return (
      <>
        <div onClick={onClose} style={{ position: 'absolute', inset: 0, background: 'rgba(0,0,0,.5)', zIndex: 60 }} />
        <div style={{ position: 'absolute', top: 0, right: 0, bottom: 0, width: 480, background: 'var(--bg-1)', borderLeft: '1px solid var(--border)', zIndex: 70, padding: 24, color: 'var(--text-2)' }}>
          Cargando {taskId}…
        </div>
      </>
    );
  }
  const t = { id: task.id, title: task.title, status: task.status, prio: task.priority, phase: task.phaseId, assignee: task.assigneeId };

  const comments = (task.comments || []).map((c) => ({
    actor: c.authorId,
    mins: minsAgo(c.createdAt),
    text: c.bodyMd,
  }));

  const desc = task.description || '_(Sin descripción)_';

  const submit = async () => {
    if (!draft.trim()) return;
    setPosting(true);
    try {
      await window.postComment(taskId, draft);
      setDraft('');
      const fresh = await window.fetchTask(taskId);
      setTask(fresh);
    } catch (e) { console.error(e); }
    finally { setPosting(false); }
  };

  // Inline editing of status / priority / assignee / phase via popovers.
  const patchField = async (patch) => {
    setOpenPop(null);
    try {
      const fresh = await window.patchTask(taskId, patch);
      setTask(fresh);
    } catch (e) { console.error(e); }
  };
  const phaseObj = (window.PHASES || []).find((p) => p.id === task.phaseId);

  return (
    <>
      <div onClick={onClose} style={{ position: 'absolute', inset: 0, background: 'rgba(0,0,0,.5)', zIndex: 60, animation: 'fade-in 150ms' }} />
      <div style={{
        position: 'absolute', top: 0, right: 0, bottom: 0, width: 480,
        background: 'var(--bg-1)', borderLeft: '1px solid var(--border)', zIndex: 70,
        display: 'flex', flexDirection: 'column',
        animation: 'slide-in-right 220ms cubic-bezier(.2,.7,.3,1)',
      }}>
        {/* header */}
        <div style={{ padding: '14px 18px', borderBottom: '1px solid var(--border)' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
            <span className="mono" style={{ fontSize: 11.5, color: 'var(--text-3)' }}>{t.id}</span>
            <StatusSelect status={t.status} anchorRef={statusRef} onClick={() => setOpenPop('status')} />
            <div style={{ flex: 1 }} />
            <button className="btn-reset" style={{ width: 26, height: 26, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 5, color: 'var(--text-2)' }}>
              <Icon name="more-horizontal" size={15} />
            </button>
            <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={{ fontSize: 17, fontWeight: 600, color: 'var(--text-0)', letterSpacing: -0.2, lineHeight: 1.35 }}>{t.title}</div>
        </div>

        {/* body */}
        <div style={{ flex: 1, overflowY: 'auto' }}>
          {/* Metadata */}
          <DrawerSection title="Metadata">
            <div style={{ display: 'grid', gridTemplateColumns: '90px 1fr', rowGap: 10, columnGap: 12, fontSize: 12.5 }}>
              <MetaLabel>Asignado a</MetaLabel>
              <button ref={assigneeRef} className="btn-reset" onClick={() => setOpenPop('assignee')}
                style={{ display: 'flex', alignItems: 'center', gap: 7, padding: '2px 6px', borderRadius: 5, textAlign: 'left' }}
                onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg-2)'}
                onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                {t.assignee
                  ? (<><Avatar actor={t.assignee} size={20} /><span style={{ color: 'var(--text-0)' }}>{ACTORS[t.assignee].name}</span></>)
                  : (<><Icon name="user-x" size={14} color="var(--text-3)" /><span style={{ color: 'var(--text-3)' }}>Sin asignar</span></>)}
                <Icon name="chevron-down" size={11} color="var(--text-3)" />
              </button>
              <MetaLabel>Fase</MetaLabel>
              <button ref={phaseRef} className="btn-reset" onClick={() => setOpenPop('phase')}
                style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '2px 6px', borderRadius: 5, textAlign: 'left' }}
                onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg-2)'}
                onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                {phaseObj
                  ? <Tag mono>{phaseObj.id} · {phaseObj.name}</Tag>
                  : <span style={{ color: 'var(--text-3)' }}>Sin fase</span>}
                <Icon name="chevron-down" size={11} color="var(--text-3)" />
              </button>
              <MetaLabel>Prioridad</MetaLabel>
              <button ref={priorityRef} className="btn-reset" onClick={() => setOpenPop('priority')}
                style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '2px 6px', borderRadius: 5, textAlign: 'left' }}
                onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg-2)'}
                onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                <PriorityChip p={t.prio} />
                <Icon name="chevron-down" size={11} color="var(--text-3)" />
              </button>
              <MetaLabel>Fecha objetivo</MetaLabel>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, color: task.dueAt ? 'var(--text-1)' : 'var(--text-3)' }}>
                <Icon name="calendar" size={12} />
                <span style={{ fontSize: 12.5 }}>{task.dueAt ? new Date(task.dueAt).toLocaleDateString('es', { day: 'numeric', month: 'short', year: 'numeric' }) : 'Sin fecha'}</span>
              </div>
              <MetaLabel>Horas</MetaLabel>
              <HoursInputs task={task} onChange={async (patch) => {
                try {
                  const fresh = await window.patchTask(task.id, patch);
                  setTask(fresh);
                } catch (e) { console.error(e); alert(e.message || 'No se pudo actualizar'); }
              }} />
            </div>
          </DrawerSection>

          <DrawerSection title="Descripción" right={
            <button className="btn-reset" onClick={() => setShowPreview(p => !p)} style={{ display: 'flex', alignItems: 'center', gap: 4, color: 'var(--text-2)', fontSize: 11 }}>
              <Icon name={showPreview ? 'pencil-line' : 'eye'} size={11} /> {showPreview ? 'Editar' : 'Preview'}
            </button>
          }>
            <div style={{ background: 'var(--bg-0)', border: '1px solid var(--border)', borderRadius: 8, padding: '12px 14px' }}>
              {showPreview
                ? <div style={{ fontSize: 12.5 }}><RenderedMd md={desc} /></div>
                : <pre style={{ margin: 0, fontFamily: 'var(--font-mono)', fontSize: 12, lineHeight: 1.6, color: 'var(--text-0)', whiteSpace: 'pre-wrap' }}>{desc}</pre>}
            </div>
          </DrawerSection>

          <DrawerSection title="Dependencias">
            <BlockedBySection task={task} onChange={async (next) => {
              try {
                await window.patchTask(task.id, { blockedBy: next });
                const fresh = await window.fetchTask(taskId);
                setTask(fresh);
              } catch (e) { console.error(e); alert(e.message || 'No se pudo actualizar'); }
            }} />
          </DrawerSection>

          <DrawerSection title="Adjuntos del Vault">
            <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
              {(task.attachments || []).length === 0 && (
                <div style={{ fontSize: 12, color: 'var(--text-3)', fontStyle: 'italic' }}>Sin adjuntos</div>
              )}
              {(task.attachments || []).map(p => (
                <a key={p} className="mono" style={{
                  display: 'flex', alignItems: 'center', gap: 8,
                  padding: '6px 8px', background: 'var(--bg-2)', border: '1px solid var(--border)',
                  borderRadius: 6, color: 'var(--accent)', fontSize: 11.5, textDecoration: 'none',
                  cursor: 'pointer',
                }}>
                  <Icon name="file-text" size={12} color="var(--text-2)" />
                  {p}
                </a>
              ))}
              <button className="btn-reset" style={{ display: 'flex', alignItems: 'center', gap: 6, color: 'var(--text-3)', fontSize: 11.5, padding: '6px 8px', border: '1px dashed var(--border)', borderRadius: 6, marginTop: 4 }}>
                <Icon name="paperclip" size={11} /> Adjuntar del Vault
              </button>
            </div>
          </DrawerSection>

          <DrawerSection title={`Comentarios (${comments.length})`}>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
              {comments.map((c, i) => (
                <div key={i} style={{ display: 'flex', gap: 10 }}>
                  <Avatar actor={c.actor} size={26} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 4 }}>
                      <span style={{ fontSize: 12.5, fontWeight: 500, color: 'var(--text-0)' }}>{ACTORS[c.actor].name}</span>
                      <span style={{ fontSize: 11, color: 'var(--text-3)' }}>{relTime(c.mins)}</span>
                    </div>
                    <div style={{ fontSize: 12.5, color: 'var(--text-1)', lineHeight: 1.55 }}>{c.text}</div>
                  </div>
                </div>
              ))}
              <div style={{
                background: 'var(--bg-0)', border: '1px solid var(--border)', borderRadius: 8,
                padding: '8px 10px',
              }}>
                <textarea placeholder="Comentar… (Markdown)" rows={2}
                  value={draft}
                  onChange={(e) => setDraft(e.target.value)}
                  onKeyDown={(e) => { if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { e.preventDefault(); submit(); } }}
                  style={{
                    width: '100%', background: 'transparent', border: 'none', resize: 'none',
                    color: 'var(--text-0)', fontSize: 12.5, fontFamily: 'inherit', outline: 'none',
                  }} />
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 4 }}>
                  <div style={{ display: 'flex', gap: 2 }}>
                    {['bold', 'italic', 'code', 'link', 'paperclip'].map(i => (
                      <button key={i} className="btn-reset" style={{ width: 22, height: 22, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 4, color: 'var(--text-3)' }}>
                        <Icon name={i} size={11} />
                      </button>
                    ))}
                  </div>
                  <Button variant="primary" size="sm" onClick={submit} disabled={posting || !draft.trim()}>{posting ? 'Enviando…' : 'Comentar'}</Button>
                </div>
              </div>
            </div>
          </DrawerSection>

          <div style={{ borderTop: '1px solid var(--border)' }}>
            <button className="btn-reset" onClick={() => setHistoryOpen(o => !o)} style={{
              display: 'flex', alignItems: 'center', justifyContent: 'space-between',
              width: '100%', padding: '14px 18px', color: 'var(--text-1)', fontSize: 12.5, fontWeight: 500,
            }}>
              <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                <Icon name="history" size={13} /> Historial · 8 cambios
              </span>
              <Icon name={historyOpen ? 'chevron-down' : 'chevron-right'} size={13} />
            </button>
            {historyOpen && (
              <div style={{ padding: '0 18px 16px', display: 'flex', flexDirection: 'column', gap: 8, fontSize: 11.5, color: 'var(--text-2)' }}>
                {(task.activity || []).length === 0
                  ? <div style={{ fontSize: 11.5, color: 'var(--text-3)', fontStyle: 'italic', padding: '4px 0' }}>Sin actividad registrada.</div>
                  : (task.activity || []).map((a, i) => (
                    <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                      <Avatar actor={a.actorId} size={16} />
                      <span style={{ color: 'var(--text-0)' }}>{ACTORS[a.actorId]?.name || a.actorId}</span>
                      <span>{a.action.replace(/_/g, ' ')}</span>
                      <span style={{ color: 'var(--text-3)', marginLeft: 'auto' }}>{relTime(minsAgo(a.createdAt))}</span>
                    </div>
                  ))
                }
              </div>
            )}
          </div>
        </div>
      </div>

      {openPop === 'status' && <StatusPopover value={t.status} anchorRef={statusRef} onClose={() => setOpenPop(null)} onPick={(s) => patchField({ status: s })} />}
      {openPop === 'assignee' && <AssigneePopover value={t.assignee} anchorRef={assigneeRef} onClose={() => setOpenPop(null)} onPick={(a) => patchField({ assigneeId: a })} />}
      {openPop === 'phase' && <PhasePopover value={t.phase} anchorRef={phaseRef} onClose={() => setOpenPop(null)} onPick={(p) => patchField({ phaseId: p })} />}
      {openPop === 'priority' && <PriorityPopover value={t.prio} anchorRef={priorityRef} onClose={() => setOpenPop(null)} onPick={(p) => patchField({ priority: p })} />}
    </>
  );
}

function MetaLabel({ children }) {
  return <span style={{ color: 'var(--text-3)', fontSize: 11.5, paddingTop: 3 }}>{children}</span>;
}

function DrawerSection({ title, right, children }) {
  return (
    <div style={{ padding: '16px 18px', borderBottom: '1px solid var(--border)' }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
        <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-2)', textTransform: 'uppercase', letterSpacing: 0.7 }}>{title}</div>
        {right}
      </div>
      {children}
    </div>
  );
}

function StatusSelect({ status, anchorRef, onClick }) {
  const m = STATUS_META[status];
  return (
    <button ref={anchorRef} className="btn-reset" onClick={onClick} style={{
      display: 'flex', alignItems: 'center', gap: 6,
      padding: '3px 9px', height: 22,
      background: m.bg, color: m.color,
      border: `1px solid ${m.color}33`,
      borderRadius: 4, fontSize: 11, fontWeight: 600,
      textTransform: 'uppercase', letterSpacing: 0.5,
      cursor: 'pointer',
    }}>
      <StatusDot s={status} />
      {m.label}
      <Icon name="chevron-down" size={11} />
    </button>
  );
}

function DepRow({ id, title, status, rel }) {
  const m = STATUS_META[status];
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 10,
      padding: '8px 10px', background: 'var(--bg-2)', border: '1px solid var(--border)', borderRadius: 6,
    }}>
      <span style={{ fontSize: 10.5, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 0.5, minWidth: 70 }}>{rel}</span>
      <span className="mono" style={{ fontSize: 10.5, color: 'var(--text-3)' }}>{id}</span>
      <span style={{ flex: 1, fontSize: 12, color: 'var(--text-0)' }}>{title}</span>
      <Badge color={m.color} bg={m.bg} dot>{m.label.toLowerCase()}</Badge>
    </div>
  );
}

// ── Onboarding ──────────────────────────────────────────────────────
function Onboarding({ step = 1, onChange }) {
  return (
    <div style={{ position: 'absolute', inset: 0, background: 'var(--bg-0)', zIndex: 200, display: 'flex', flexDirection: 'column' }}>
      <div style={{ height: 48, display: 'flex', alignItems: 'center', padding: '0 20px', borderBottom: '1px solid var(--border)' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <div style={{ width: 18, height: 18, background: 'var(--text-0)', borderRadius: 4, position: 'relative' }}>
            <div style={{ position: 'absolute', inset: 4, background: 'var(--bg-0)', borderRadius: 1 }} />
          </div>
          <span style={{ fontSize: 13, fontWeight: 600 }}>Workspace</span>
        </div>
        <div style={{ flex: 1 }} />
        <span style={{ fontSize: 12, color: 'var(--text-2)' }}>Setup · paso {step} de 3</span>
      </div>
      <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 40 }}>
        {step === 1 && <OnboardingIdentity onContinue={() => onChange(2)} />}
        {step === 2 && <OnboardingVault onContinue={() => onChange(3)} onBack={() => onChange(1)} />}
        {step === 3 && <OnboardingPhases onContinue={() => onChange(0)} onBack={() => onChange(2)} />}
      </div>
    </div>
  );
}

function OnboardingIdentity({ onContinue }) {
  const [picked, setPicked] = React.useState('founder');
  return (
    <div style={{ width: 760, maxWidth: '100%' }}>
      <h1 style={{ fontSize: 30, fontWeight: 600, letterSpacing: -0.6, textAlign: 'center', margin: 0 }}>Bienvenido a Workspace</h1>
      <div style={{ fontSize: 14, color: 'var(--text-2)', textAlign: 'center', marginTop: 8, marginBottom: 36 }}>Selecciona tu identidad para empezar</div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 14 }}>
        {ACTOR_LIST.map(id => {
          const a = ACTORS[id];
          const sel = picked === id;
          return (
            <button key={id} className="btn-reset" onClick={() => setPicked(id)}
              style={{
                display: 'flex', alignItems: 'flex-start', gap: 16,
                padding: 20, textAlign: 'left',
                background: 'var(--bg-1)',
                border: `1px solid ${sel ? a.color : 'var(--border)'}`,
                boxShadow: sel ? `0 0 0 3px color-mix(in srgb, ${a.color} 18%, transparent)` : 'none',
                borderRadius: 'var(--r-lg)',
                transition: 'border-color var(--t-fast), box-shadow var(--t-fast)',
              }}>
              <Avatar actor={id} size={44} />
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 15, fontWeight: 600, color: 'var(--text-0)' }}>{a.name}</div>
                <div style={{ fontSize: 12, color: a.color, fontWeight: 500, marginTop: 2 }}>{a.role}</div>
                <div style={{ fontSize: 12.5, color: 'var(--text-2)', marginTop: 8, lineHeight: 1.5 }}>{a.desc}</div>
              </div>
              {sel && <Icon name="check-circle-2" size={18} color={a.color} />}
            </button>
          );
        })}
      </div>
      <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 28 }}>
        <Button variant="primary" size="lg" iconRight="arrow-right" onClick={onContinue}>Continuar</Button>
      </div>
    </div>
  );
}

function OnboardingVault({ onContinue, onBack }) {
  return (
    <div style={{ width: 620 }}>
      <h2 style={{ fontSize: 24, fontWeight: 600, letterSpacing: -0.4, margin: 0, textAlign: 'center' }}>Estructura inicial del Vault</h2>
      <div style={{ fontSize: 13.5, color: 'var(--text-2)', textAlign: 'center', marginTop: 8, marginBottom: 28 }}>
        Estas 7 carpetas son la base de organización del proyecto. Puedes editarlas después.
      </div>
      <div style={{ background: 'var(--bg-1)', border: '1px solid var(--border)', borderRadius: 'var(--r-lg)', padding: 8 }}>
        {VAULT_TREE.map(f => (
          <div key={f.id} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 12px', borderBottom: '1px solid var(--border)' }}>
            <div style={{ width: 28, height: 28, background: 'var(--bg-2)', borderRadius: 6, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-1)' }}>
              <Icon name={f.icon} size={14} />
            </div>
            <div style={{ flex: 1 }}>
              <div className="mono" style={{ fontSize: 13, color: 'var(--text-0)' }}>/{f.id}</div>
              <div style={{ fontSize: 11.5, color: 'var(--text-3)', marginTop: 1 }}>
                {f.id === 'specs' && 'Especificaciones técnicas y diseño de sistema'}
                {f.id === 'sesiones' && 'Notas de cada sesión de trabajo'}
                {f.id === 'decisiones' && 'ADRs — decisiones técnicas registradas'}
                {f.id === 'casos' && 'Casos de uso y edge cases documentados'}
                {f.id === 'negocio' && 'Estrategia comercial y roadmap'}
                {f.id === 'genaro' && 'Notas internas del Technical Lead'}
                {f.id === 'inbox' && 'Capturas rápidas sin clasificar'}
              </div>
            </div>
          </div>
        ))}
      </div>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 22 }}>
        <Button variant="ghost" onClick={onBack} icon="arrow-left">Atrás</Button>
        <Button variant="primary" size="lg" iconRight="arrow-right" onClick={onContinue}>Crear estructura recomendada</Button>
      </div>
    </div>
  );
}

function OnboardingPhases({ onContinue, onBack }) {
  return (
    <div style={{ width: 620 }}>
      <h2 style={{ fontSize: 24, fontWeight: 600, letterSpacing: -0.4, margin: 0, textAlign: 'center' }}>Importar fases del proyecto</h2>
      <div style={{ fontSize: 13.5, color: 'var(--text-2)', textAlign: 'center', marginTop: 8, marginBottom: 28 }}>
        Vamos a crear tareas iniciales para todas las fases pendientes.
      </div>
      <div style={{ background: 'var(--bg-1)', border: '1px solid var(--border)', borderRadius: 'var(--r-lg)', maxHeight: 360, overflowY: 'auto' }}>
        {PHASES.slice(1).map(p => (
          <div key={p.id} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 14px', borderBottom: '1px solid var(--border)' }}>
            <Checkbox checked={true} onChange={() => {}} />
            <span className="mono" style={{ fontSize: 12, color: 'var(--text-2)', minWidth: 44 }}>{p.id}</span>
            <span style={{ flex: 1, fontSize: 13, color: 'var(--text-0)' }}>{p.name}</span>
            <span className="mono" style={{ fontSize: 11.5, color: 'var(--text-3)' }}>{p.total} tareas</span>
          </div>
        ))}
      </div>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 22 }}>
        <Button variant="ghost" onClick={onBack} icon="arrow-left">Atrás</Button>
        <Button variant="primary" size="lg" iconRight="check" onClick={onContinue}>Importar fases</Button>
      </div>
    </div>
  );
}

// ── Command palette (⌘K) ────────────────────────────────────────────
// Live search over /api/docs/search + window.TASKS (client-side filter) +
// static actions. Use ↑↓ to navigate, ↵ to activate, esc to close.
function CommandPalette({ onClose, onNavigate, onOpenTask, onOpenDoc, onCreateTask, onCreateDoc }) {
  const [q, setQ] = React.useState('');
  const [docHits, setDocHits] = React.useState([]);
  const [searching, setSearching] = React.useState(false);
  const [cursor, setCursor] = React.useState(0);

  // Debounced doc search.
  React.useEffect(() => {
    if (!q.trim()) { setDocHits([]); return; }
    const handle = setTimeout(async () => {
      setSearching(true);
      try {
        const res = await window.searchDocs(q.trim(), 8);
        setDocHits(Array.isArray(res) ? res : (res?.hits || []));
      } catch { setDocHits([]); }
      finally { setSearching(false); }
    }, 180);
    return () => clearTimeout(handle);
  }, [q]);

  // Local task filter — TASKS is small enough to scan every keystroke.
  const taskHits = React.useMemo(() => {
    if (!q.trim()) return [];
    const needle = q.trim().toLowerCase();
    return (window.TASKS || [])
      .filter((t) => t.title.toLowerCase().includes(needle) || t.id.toLowerCase().includes(needle))
      .slice(0, 8);
  }, [q]);

  const actions = React.useMemo(() => [
    { kind: 'cmd', icon: 'plus', title: 'Crear nueva tarea', sub: 'Abre el formulario de nueva tarea', run: () => { onCreateTask(); onClose(); }, kbd: 'N' },
    { kind: 'cmd', icon: 'file-plus', title: 'Crear nuevo documento', sub: 'Abre el formulario de nuevo documento', run: () => { onCreateDoc(); onClose(); }, kbd: 'D' },
    { kind: 'nav', icon: 'kanban', title: 'Ir a Tablero', sub: 'tracker.kanban', run: () => { onNavigate('tracker.kanban'); onClose(); } },
    { kind: 'nav', icon: 'layers', title: 'Ir a Fases', sub: 'tracker.phases', run: () => { onNavigate('tracker.phases'); onClose(); } },
    { kind: 'nav', icon: 'gantt-chart', title: 'Ir a Timeline', sub: 'tracker.timeline', run: () => { onNavigate('tracker.timeline'); onClose(); } },
    { kind: 'nav', icon: 'user-circle', title: 'Ir a Mis tareas', sub: 'tracker.mine', run: () => { onNavigate('tracker.mine'); onClose(); } },
    { kind: 'nav', icon: 'library', title: 'Ir al Vault', sub: 'vault.home', run: () => { onNavigate('vault.home'); onClose(); } },
  ].filter((a) => !q.trim() || a.title.toLowerCase().includes(q.toLowerCase())), [q, onNavigate, onCreateTask, onCreateDoc, onClose]);

  // Flatten into a single navigable list (preserves group order).
  const flat = React.useMemo(() => {
    const out = [];
    docHits.forEach((d) => out.push({
      kind: 'doc', icon: 'file-text',
      title: d.title || d.path,
      sub: '/' + d.path,
      html: d.snippet || null,
      run: () => { onOpenDoc(d.path); onClose(); },
    }));
    taskHits.forEach((t) => out.push({
      kind: 'task', icon: t.status === 'closed' ? 'check-circle-2' : 'circle-dot',
      title: t.title,
      sub: `${t.id} · ${STATUS_META[t.status]?.label || t.status}${t.assignee ? ' · ' + (ACTORS[t.assignee]?.name || t.assignee) : ''}`,
      run: () => { onOpenTask(t.id); onClose(); },
    }));
    actions.forEach((a) => out.push(a));
    return out;
  }, [docHits, taskHits, actions, onOpenDoc, onOpenTask, onClose]);

  React.useEffect(() => { setCursor(0); }, [flat.length]);

  const onKeyDown = (e) => {
    if (e.key === 'ArrowDown') { e.preventDefault(); setCursor((c) => Math.min(flat.length - 1, c + 1)); }
    else if (e.key === 'ArrowUp') { e.preventDefault(); setCursor((c) => Math.max(0, c - 1)); }
    else if (e.key === 'Enter') { e.preventDefault(); flat[cursor]?.run?.(); }
    else if (e.key === 'Escape') { onClose(); }
  };

  const groupOrder = ['doc', 'task', 'cmd', 'nav'];
  const groupLabels = { doc: 'Documentos', task: 'Tareas', cmd: 'Acciones', nav: 'Navegación' };
  let runningIdx = 0;

  return (
    <>
      <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.5)', zIndex: 100, animation: 'fade-in 120ms', backdropFilter: 'blur(2px)' }} />
      <div style={{
        position: 'fixed', top: '12%', left: '50%', transform: 'translateX(-50%)',
        width: 620, maxHeight: '70vh',
        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={{ display: 'flex', alignItems: 'center', gap: 10, padding: '14px 16px', borderBottom: '1px solid var(--border)' }}>
          <Icon name="search" size={15} color="var(--text-2)" />
          <input autoFocus placeholder="Buscar documentos, tareas, ejecutar comandos…"
            value={q} onChange={(e) => setQ(e.target.value)} onKeyDown={onKeyDown}
            style={{
              flex: 1, background: 'transparent', border: 'none', outline: 'none',
              color: 'var(--text-0)', fontSize: 14, fontFamily: 'inherit',
            }} />
          {searching && <Icon name="loader-2" size={13} color="var(--text-3)" className="spin" />}
          <span className="mono" style={{ padding: '2px 6px', background: 'var(--bg-2)', border: '1px solid var(--border)', borderRadius: 4, color: 'var(--text-2)', fontSize: 10.5 }}>esc</span>
        </div>
        <div style={{ flex: 1, overflowY: 'auto', padding: '6px 0' }}>
          {flat.length === 0 && (
            <div style={{ padding: '24px 16px', textAlign: 'center', color: 'var(--text-3)', fontSize: 12.5 }}>
              {q.trim() ? `Sin resultados para "${q}".` : 'Empieza a escribir o usa una de las acciones.'}
            </div>
          )}
          {groupOrder.map((kind) => {
            const its = flat.filter((i) => i.kind === kind);
            if (!its.length) return null;
            return (
              <div key={kind}>
                <div style={{ padding: '8px 16px 4px', fontSize: 10.5, fontWeight: 600, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 0.7 }}>{groupLabels[kind]}</div>
                {its.map((it) => {
                  const idx = runningIdx++;
                  const active = idx === cursor;
                  return (
                    <div key={idx}
                      onMouseEnter={() => setCursor(idx)}
                      onClick={() => it.run?.()}
                      style={{
                        display: 'flex', alignItems: 'center', gap: 12, padding: '8px 16px',
                        background: active ? 'var(--accent-dim)' : 'transparent',
                        cursor: 'pointer',
                        borderLeft: active ? '2px solid var(--accent)' : '2px solid transparent',
                      }}>
                      <Icon name={it.icon} size={14} color={active ? 'var(--accent)' : 'var(--text-2)'} />
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <div style={{ fontSize: 13, color: active ? 'var(--text-0)' : 'var(--text-0)' }}>{it.title}</div>
                        {it.html
                          ? <div className="mono" style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} dangerouslySetInnerHTML={{ __html: it.html }} />
                          : <div className={(it.sub || '').includes('/') || /^T-/.test(it.sub || '') ? 'mono' : ''} style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 1 }}>{it.sub}</div>}
                      </div>
                      {it.kbd && <span className="mono" style={{ padding: '2px 6px', background: 'var(--bg-2)', border: '1px solid var(--border)', borderRadius: 4, color: 'var(--text-2)', fontSize: 10.5 }}>{it.kbd}</span>}
                    </div>
                  );
                })}
              </div>
            );
          })}
        </div>
        <div style={{ padding: '8px 16px', borderTop: '1px solid var(--border)', display: 'flex', gap: 16, fontSize: 11, color: 'var(--text-3)' }}>
          <span>↑↓ navegar</span>
          <span>↵ abrir</span>
          <span>esc cerrar</span>
        </div>
      </div>
    </>
  );
}

// ── Hours inputs ─────────────────────────────────────────────────────
//
// Two inline editable fields: "Est." (estimatedHours) and "Real"
// (actualHours). Both decimal, both nullable. Empty string = clear (null).
// Saves on blur or Enter; Escape reverts.
function HoursInputs({ task, onChange }) {
  const [est, setEst] = React.useState(task.estimatedHours == null ? '' : String(task.estimatedHours));
  const [real, setReal] = React.useState(task.actualHours == null ? '' : String(task.actualHours));
  // Re-sync when the task prop changes (e.g. after WS event refetch).
  React.useEffect(() => {
    setEst(task.estimatedHours == null ? '' : String(task.estimatedHours));
    setReal(task.actualHours == null ? '' : String(task.actualHours));
  }, [task.estimatedHours, task.actualHours]);

  const commit = (field, raw, originalValue) => {
    const trimmed = (raw ?? '').trim();
    const next = trimmed === '' ? null : Number(trimmed);
    if (trimmed !== '' && (!Number.isFinite(next) || next < 0 || next >= 10_000)) {
      alert('Las horas deben ser un número entre 0 y 9999.99');
      // Revert input.
      if (field === 'estimatedHours') setEst(originalValue == null ? '' : String(originalValue));
      else setReal(originalValue == null ? '' : String(originalValue));
      return;
    }
    if ((next == null && originalValue == null) || (next != null && next === originalValue)) return;
    onChange({ [field]: next });
  };

  const numInput = (value, setValue, onBlur, placeholder) => (
    <input type="number" step="0.5" min="0" max="9999.99" inputMode="decimal"
      value={value} onChange={(e) => setValue(e.target.value)}
      onBlur={onBlur}
      onKeyDown={(e) => {
        if (e.key === 'Enter') { e.currentTarget.blur(); }
        if (e.key === 'Escape') {
          setValue(placeholder.original == null ? '' : String(placeholder.original));
          e.currentTarget.blur();
        }
      }}
      placeholder={placeholder.text}
      style={{
        width: 64, height: 24, padding: '0 6px',
        background: 'var(--bg-0)', border: '1px solid var(--border)',
        borderRadius: 5, color: 'var(--text-0)', fontSize: 12,
        fontFamily: 'var(--font-mono)', outline: 'none', textAlign: 'right',
      }} />
  );
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
      <span style={{ fontSize: 11, color: 'var(--text-3)' }}>Est.</span>
      {numInput(est, setEst, () => commit('estimatedHours', est, task.estimatedHours),
        { text: '—', original: task.estimatedHours })}
      <span style={{ fontSize: 11, color: 'var(--text-3)' }}>h · Real</span>
      {numInput(real, setReal, () => commit('actualHours', real, task.actualHours),
        { text: '—', original: task.actualHours })}
      <span style={{ fontSize: 11, color: 'var(--text-3)' }}>h</span>
      {task.estimatedHours != null && task.actualHours != null && task.estimatedHours > 0 && (
        <span style={{
          fontSize: 11, color: task.actualHours > task.estimatedHours ? 'var(--warn)' : 'var(--success)',
          marginLeft: 4,
        }}>
          {Math.round((task.actualHours / task.estimatedHours) * 100)}%
        </span>
      )}
    </div>
  );
}

// ── BlockedBy chips ──────────────────────────────────────────────────
//
// Renders each blocker as a chip: id + status badge + ✕ to remove. Plus an
// add input that accepts a `T-NNN` id (autocomplete from the project's
// task list). Mutations call `onChange(nextArray)` — the parent decides
// how to persist.
//
// Note: blockedBy is HISTORICAL (decision D3-(ii) in the design). The
// chip stays even if the blocker is closed; instead we render a 🔓
// badge on closed blockers, and the parent-level `isReady` flag flips
// to true. Removing a chip is a deliberate "this dependency was wrong /
// no longer relevant" action, distinct from "the blocker is done".
function BlockedBySection({ task, onChange }) {
  const [adding, setAdding] = React.useState(false);
  const [pickerQuery, setPickerQuery] = React.useState('');
  const inputRef = React.useRef(null);

  const blockers = task.blockers || [];
  const blocking = task.blocking || [];
  const blockedByIds = Array.isArray(task.blockedBy) ? task.blockedBy : [];

  React.useEffect(() => {
    if (adding) inputRef.current?.focus();
  }, [adding]);

  const remove = (id) => {
    const next = blockedByIds.filter((x) => x !== id);
    onChange(next);
  };
  const add = (id) => {
    if (!id) return;
    if (blockedByIds.includes(id)) { setAdding(false); setPickerQuery(''); return; }
    const next = [...blockedByIds, id];
    onChange(next);
    setAdding(false);
    setPickerQuery('');
  };

  // Autocomplete candidates: project tasks whose id contains the query,
  // excluding self and already-listed blockers.
  const candidates = React.useMemo(() => {
    const q = pickerQuery.trim().toLowerCase();
    return (window.TASKS || [])
      .filter((t) => t.id !== task.id && !blockedByIds.includes(t.id))
      .filter((t) => !q || t.id.toLowerCase().includes(q) || (t.title || '').toLowerCase().includes(q))
      .slice(0, 8);
  }, [pickerQuery, task.id, blockedByIds.join(',')]);

  const isReady = blockers.length === 0
    || blockers.every((b) => b.status === 'closed' || b.status === 'discarded' || b.missing);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      {/* Bloqueada por */}
      <div>
        <div style={{ fontSize: 11, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 6 }}>
          Bloqueada por
          {blockers.length > 0 && (
            <span style={{
              marginLeft: 8, fontSize: 10.5, padding: '1px 6px',
              borderRadius: 4, background: isReady ? 'rgba(16,185,129,.12)' : 'rgba(245,158,11,.15)',
              color: isReady ? 'var(--success)' : 'var(--warn)', textTransform: 'none', letterSpacing: 0,
            }}>
              {isReady ? '✓ lista para empezar' : `${blockers.filter((b) => b.status !== 'closed' && b.status !== 'discarded' && !b.missing).length} pendientes`}
            </span>
          )}
        </div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
          {blockers.length === 0 && !adding && (
            <span style={{ fontSize: 12, color: 'var(--text-3)', fontStyle: 'italic' }}>Sin bloqueadores</span>
          )}
          {blockers.map((b) => {
            const closed = b.status === 'closed' || b.status === 'discarded' || b.missing;
            return (
              <span key={b.id} title={b.title || b.id} style={{
                display: 'inline-flex', alignItems: 'center', gap: 6,
                padding: '4px 6px 4px 8px',
                background: 'var(--bg-2)', border: '1px solid var(--border)',
                borderRadius: 6, fontSize: 11.5,
                color: closed ? 'var(--text-2)' : 'var(--text-0)',
                textDecoration: closed ? 'line-through' : 'none',
                opacity: b.missing ? 0.55 : 1,
              }}>
                <Icon name={closed ? 'check-circle-2' : 'circle'} size={11} color={closed ? 'var(--success)' : 'var(--text-3)'} />
                <span className="mono">{b.id}</span>
                <span style={{ maxWidth: 180, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{b.title}</span>
                <button className="btn-reset" onClick={() => remove(b.id)}
                  title="Quitar bloqueador (mutación deliberada — no re-agregar al cerrar la tarea)"
                  style={{ width: 16, height: 16, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 4, color: 'var(--text-3)' }}>
                  <Icon name="x" size={11} />
                </button>
              </span>
            );
          })}
          {adding ? (
            <span style={{ position: 'relative' }}>
              <input ref={inputRef} value={pickerQuery}
                onChange={(e) => setPickerQuery(e.target.value)}
                onKeyDown={(e) => {
                  if (e.key === 'Escape') { setAdding(false); setPickerQuery(''); }
                  if (e.key === 'Enter' && candidates[0]) add(candidates[0].id);
                }}
                placeholder="T-NNN o título…"
                style={{
                  height: 26, padding: '0 8px', minWidth: 180,
                  background: 'var(--bg-0)', border: '1px solid var(--border-strong)',
                  borderRadius: 6, color: 'var(--text-0)', fontSize: 12, outline: 'none',
                }} />
              {candidates.length > 0 && (
                <div style={{
                  position: 'absolute', top: 30, left: 0, zIndex: 30,
                  background: 'var(--bg-1)', border: '1px solid var(--border-strong)',
                  borderRadius: 6, padding: 4, minWidth: 280, maxHeight: 240, overflowY: 'auto',
                  boxShadow: '0 8px 22px rgba(0,0,0,.45)',
                }}>
                  {candidates.map((t) => (
                    <button key={t.id} className="btn-reset"
                      onMouseDown={(e) => { e.preventDefault(); add(t.id); }}
                      style={{
                        display: 'flex', alignItems: 'center', gap: 8, width: '100%',
                        padding: '5px 8px', borderRadius: 4, fontSize: 12, color: 'var(--text-1)',
                        textAlign: 'left',
                      }}
                      onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg-2)'}
                      onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                      <span className="mono" style={{ color: 'var(--text-3)' }}>{t.id}</span>
                      <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{t.title}</span>
                    </button>
                  ))}
                </div>
              )}
            </span>
          ) : (
            <button className="btn-reset" onClick={() => setAdding(true)}
              style={{ display: 'flex', alignItems: 'center', gap: 6, color: 'var(--text-3)', fontSize: 11.5, padding: '4px 8px', border: '1px dashed var(--border)', borderRadius: 6 }}>
              <Icon name="plus" size={11} /> Agregar bloqueador
            </button>
          )}
        </div>
      </div>

      {/* Bloqueando a (reverse) */}
      {blocking.length > 0 && (
        <div>
          <div style={{ fontSize: 11, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 6 }}>
            Bloqueando a
          </div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
            {blocking.map((b) => (
              <span key={b.id} title={b.title} style={{
                display: 'inline-flex', alignItems: 'center', gap: 6,
                padding: '4px 8px',
                background: 'var(--bg-2)', border: '1px solid var(--border)',
                borderRadius: 6, fontSize: 11.5, color: 'var(--text-1)',
              }}>
                <span className="mono" style={{ color: 'var(--text-3)' }}>{b.id}</span>
                <span style={{ maxWidth: 180, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{b.title}</span>
              </span>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { TaskDrawer, Onboarding, CommandPalette, BlockedBySection, HoursInputs });
