// Briefings management view — list + detail drawer.
//
// Routes (hash):
//   #/briefings          → list view
//   #/briefings/:id      → list view + drawer for that briefing on top
//
// Status state machine (from server/briefings/store.py):
//   draft → in_creative_review → in_business_review → approved
//                                                     ↑
//   any  → rejected (with reason); rejected can be reopened to draft
//   any  → archived (terminal)

const { useState, useEffect, useMemo } = React;

// ─── status pill colors ─────────────────────────────────────────────────

const STATUS_META = {
  draft:                { label: 'DRAFT',                bg: '#f4f1ec', fg: '#7a7065', border: '#d6cfc2' },
  in_creative_review:   { label: 'IN CREATIVE REVIEW',   bg: '#fdf4e3', fg: '#8a5a10', border: '#d6a85a' },
  in_business_review:   { label: 'IN BUSINESS REVIEW',   bg: '#eef3fb', fg: '#345583', border: '#9bb6dc' },
  approved:             { label: 'APPROVED',             bg: '#e8f3eb', fg: '#2c5d3a', border: '#83b893' },
  rejected:             { label: 'REJECTED',             bg: '#fdecec', fg: '#a72020', border: '#e08a8a' },
  archived:             { label: 'ARCHIVED',             bg: '#ececec', fg: '#666',    border: '#c4c4c4' },
};

function StatusPill({ status, size = 'md' }) {
  const meta = STATUS_META[status] || STATUS_META.draft;
  const padding = size === 'sm' ? '2px 7px' : '4px 10px';
  const fontSize = size === 'sm' ? 9 : 10;
  return (
    <span style={{
      display: 'inline-block',
      padding,
      borderRadius: 4,
      background: meta.bg,
      color: meta.fg,
      border: `1px solid ${meta.border}`,
      fontFamily: 'var(--font-mono)',
      fontSize,
      letterSpacing: '.06em',
      fontWeight: 500,
      textTransform: 'uppercase',
      whiteSpace: 'nowrap',
    }}>
      {meta.label}
    </span>
  );
}

// ─── tiny helpers ───────────────────────────────────────────────────────

function relTime(iso) {
  if (!iso) return '';
  const t = new Date(iso).getTime();
  const diff = Date.now() - t;
  const mins = Math.floor(diff / 60000);
  if (mins < 1) return 'just now';
  if (mins < 60) return `${mins}m ago`;
  const hrs = Math.floor(mins / 60);
  if (hrs < 24) return `${hrs}h ago`;
  const days = Math.floor(hrs / 24);
  if (days < 30) return `${days}d ago`;
  return new Date(iso).toLocaleDateString();
}

function FilterChip({ label, count, active, onClick }) {
  return (
    <button
      type="button"
      onClick={onClick}
      className="btn-reset shimmer"
      style={{
        display: 'inline-flex', alignItems: 'center', gap: 6,
        padding: '7px 14px', borderRadius: 99,
        border: `1px solid ${active ? 'transparent' : 'var(--glass-border)'}`,
        background: active ? 'var(--iris)' : 'var(--glass)',
        WebkitBackdropFilter: 'blur(20px) saturate(180%)',
        backdropFilter: 'blur(20px) saturate(180%)',
        color: active ? '#fff' : 'var(--ink-soft)',
        fontSize: 13, cursor: 'pointer',
        fontFamily: 'var(--font-serif)', fontStyle: active ? 'italic' : 'normal',
        fontWeight: active ? 500 : 400,
        boxShadow: active ? '0 6px 18px rgba(53,2,246,.25), inset 0 1px 0 rgba(255,255,255,.45)' : 'inset 0 1px 0 rgba(255,255,255,.6)',
        transition: 'all .35s var(--ease-fluid)',
      }}
    >
      {label}
      {typeof count === 'number' && (
        <span style={{
          fontFamily: 'var(--font-mono)', fontSize: 10,
          padding: '1px 6px', borderRadius: 99,
          background: active ? 'rgba(255,255,255,0.25)' : 'rgba(255,255,255,0.4)',
          color: active ? '#fff' : 'var(--ink-mute)',
          minWidth: 18, textAlign: 'center',
        }}>{count}</span>
      )}
    </button>
  );
}

// ─── BriefingsView (page) ───────────────────────────────────────────────

function BriefingsView() {
  const {
    briefings, briefingsById, briefingFilter, setBriefingFilter,
    route, navigate, createBriefing,
  } = useStore();

  // Filter chips: "All" / per status. Counts come from the unfiltered list,
  // but in this in-memory backend the list is already filtered server-side.
  // For accurate counts, we'd need a separate "all" fetch. For now: just
  // show the active state without a count.
  const statuses = ['draft', 'in_creative_review', 'in_business_review', 'approved', 'rejected'];

  return (
    <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', background: 'var(--bg)' }}>
      {/* ── Editorial header ──────────────────────────────────────────── */}
      <div style={{
        maxWidth: 1080, width: '100%', margin: '0 auto', padding: '28px 36px 0',
        display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
      }}>
        <div className="kicker">Briefings desk</div>
        <div className="kicker numeral">
          {String(briefings.length).padStart(3, '0')} on file
        </div>
      </div>

      <div style={{ maxWidth: 1080, width: '100%', margin: '0 auto', padding: '14px 36px 24px', display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', gap: 24 }}>
        <div>
          <div className="display" style={{ fontSize: 60, color: 'var(--ink)' }}>
            The <span className="iridescent" style={{ fontStyle: 'italic', fontWeight: 400 }}>brief</span> desk.
          </div>
          <div className="display-italic" style={{ fontSize: 20, color: 'var(--ink-soft)', marginTop: 8, maxWidth: 540 }}>
            Where conversations become creative direction. Filed, reviewed, and shipped.
          </div>
        </div>
        <button
          type="button"
          onClick={async () => {
            try { await createBriefing(); }
            catch (e) { console.error('[briefings] createBriefing threw', e); }
          }}
          className="btn-reset shimmer"
          style={{
            display: 'inline-flex', alignItems: 'center', gap: 10,
            padding: '11px 22px', borderRadius: 99,
            background: 'var(--iris)', color: '#fff',
            fontFamily: 'var(--font-serif)', fontStyle: 'italic',
            fontSize: 16, fontWeight: 500, letterSpacing: '-0.005em',
            whiteSpace: 'nowrap',
            boxShadow: '0 8px 24px rgba(53,2,246,.30), inset 0 1px 0 rgba(255,255,255,0.4)',
            cursor: 'pointer',
            transition: 'transform .35s var(--ease-fluid), box-shadow .35s var(--ease-fluid)',
          }}
          onMouseEnter={e => { e.currentTarget.style.transform = 'translateY(-1px)'; e.currentTarget.style.boxShadow = '0 12px 32px rgba(53,2,246,.40), inset 0 1px 0 rgba(255,255,255,0.5)'; }}
          onMouseLeave={e => { e.currentTarget.style.transform = 'none'; e.currentTarget.style.boxShadow = '0 8px 24px rgba(53,2,246,.30), inset 0 1px 0 rgba(255,255,255,0.4)'; }}
        >
          Begin a brief
          <span style={{ fontFamily: 'var(--font-mono)', fontStyle: 'normal' }}>→</span>
        </button>
      </div>

      {/* ── Filter row ────────────────────────────────────────────────── */}
      <div style={{
        maxWidth: 1080, width: '100%', margin: '0 auto', padding: '0 36px 16px',
        display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap',
      }}>
        <FilterChip
          label="All"
          active={!briefingFilter.status}
          onClick={() => setBriefingFilter({ ...briefingFilter, status: null })}
        />
        <FilterChip
          label="Active"
          active={briefingFilter.status === 'draft,in_creative_review,in_business_review'}
          onClick={() => setBriefingFilter({
            ...briefingFilter,
            status: 'draft,in_creative_review,in_business_review',
          })}
        />
        {statuses.map(s => (
          <FilterChip
            key={s}
            label={STATUS_META[s].label}
            active={briefingFilter.status === s}
            onClick={() => setBriefingFilter({ ...briefingFilter, status: s })}
          />
        ))}
      </div>

      {/* ── The index ─────────────────────────────────────────────────── */}
      <div style={{ flex: 1, overflowY: 'auto' }}>
        <div style={{ maxWidth: 1080, width: '100%', margin: '0 auto', padding: '0 36px 48px' }}>
          {briefings.length === 0 ? (
            <div className="serif-italic" style={{
              padding: '64px 0', textAlign: 'center',
              color: 'var(--ink-mute)', fontSize: 18,
            }}>
              No briefings on file. Begin one above, or ask the agent in any thread.
            </div>
          ) : (
            <div>
              {/* Column headers — kicker labels above the floating glass rows */}
              <div style={{
                display: 'grid',
                gridTemplateColumns: '36px 1fr 180px 140px 80px',
                padding: '8px 22px 12px',
                gap: 16,
              }}>
                <span className="kicker">№</span>
                <span className="kicker">Briefing</span>
                <span className="kicker">Status</span>
                <span className="kicker">Owner</span>
                <span className="kicker" style={{ textAlign: 'right' }}>Updated</span>
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                {briefings.map((b, i) => (
                  <BriefingRow
                    key={b.id}
                    index={i + 1}
                    briefing={b}
                    onClick={() => navigate(`/briefings/${b.id}`)}
                  />
                ))}
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function BriefingRow({ index, briefing, onClick }) {
  const [hover, setHover] = useState(false);
  return (
    <div
      onClick={onClick}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      className="shimmer"
      style={{
        display: 'grid',
        gridTemplateColumns: '36px 1fr 180px 140px 80px',
        padding: '20px 22px', gap: 16, alignItems: 'baseline',
        borderRadius: 'var(--radius)',
        background: 'var(--glass)',
        WebkitBackdropFilter: 'blur(24px) saturate(180%)',
        backdropFilter: 'blur(24px) saturate(180%)',
        border: `1px solid ${hover ? 'var(--glass-border-strong)' : 'var(--glass-border)'}`,
        boxShadow: hover ? 'var(--shadow-md), inset 0 1px 0 rgba(255,255,255,.85)' : 'var(--shadow-glass)',
        transform: hover ? 'translateY(-2px)' : 'none',
        cursor: 'pointer',
        transition: 'transform .35s var(--ease-fluid), box-shadow .35s var(--ease-fluid), border-color .35s var(--ease-fluid)',
      }}
    >
      <div className="numeral" style={{ fontSize: 11, color: hover ? 'var(--moss-deep)' : 'var(--ink-mute)' }}>
        {String(index).padStart(2, '0')}
      </div>
      <div style={{ minWidth: 0 }}>
        <div style={{
          fontFamily: 'var(--font-serif)', fontSize: 22,
          fontStyle: hover ? 'italic' : 'normal',
          color: 'var(--ink)', letterSpacing: '-0.012em', lineHeight: 1.2,
          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
          transition: 'font-style .15s',
        }}>{briefing.title || 'Untitled brief'}</div>
        {briefing.subtitle && (
          <div className="serif-italic" style={{
            fontSize: 14, color: 'var(--ink-soft)', marginTop: 4,
            overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
          }}>{briefing.subtitle}</div>
        )}
        {briefing.comment_count > 0 && (
          <div className="kicker" style={{ marginTop: 6 }}>
            {briefing.comment_count} {briefing.comment_count === 1 ? 'note' : 'notes'}
          </div>
        )}
      </div>
      <div>
        <StatusPill status={briefing.status} />
      </div>
      <div className="serif-italic" style={{
        fontSize: 14,
        color: briefing.owner ? 'var(--ink-soft)' : 'var(--ink-mute)',
      }}>
        {briefing.owner || 'unassigned'}
      </div>
      <div className="kicker" style={{ textAlign: 'right' }}>
        {relTime(briefing.updated_at)}
      </div>
    </div>
  );
}

// ─── Drawer ─────────────────────────────────────────────────────────────

// ─── BriefingDetailPage (full-page) ─────────────────────────────────────
//
// Replaces the old right-side drawer. Lives at /briefings/<id>. When the
// briefing has no marketing brief yet (just-created draft), the body
// shows a full-page IntakePane that runs the CD agent against the
// reserved thread_id. Once the brief lands, the body switches to the
// standard review tabs.

function BriefingDetailPage({ briefing }) {
  const { navigate, transitionBriefing, patchBriefing, commentOnBriefing, loadBriefing } = useStore();
  const intakeMode = !briefing.brief && briefing.status === 'draft';
  const [tab, setTab] = useState(intakeMode ? 'intake' : 'brief');
  const [busy, setBusy] = useState(false);
  const [showRejectModal, setShowRejectModal] = useState(false);

  // When the brief actually lands during intake, jump the user to the
  // marketing brief tab so they see what was just produced.
  useEffect(() => {
    if (briefing.brief && tab === 'intake') setTab('brief');
  }, [briefing.brief]);

  const handleTransition = async (to, reason = null) => {
    setBusy(true);
    try { await transitionBriefing(briefing.id, to, reason); }
    finally { setBusy(false); }
  };

  const tabs = intakeMode
    ? [['intake', 'Intake (Creative Director)'], ['comments', `Comments (${(briefing.comments || []).length})`], ['activity', 'Activity']]
    : [
        ['brief', 'Marketing brief'],
        ['cd_brief', 'Creative direction'],
        ['comments', `Comments (${(briefing.comments || []).length})`],
        ['activity', 'Activity'],
      ];

  return (
    <div style={{
      flex: 1, display: 'flex', flexDirection: 'column',
      height: '100vh', background: 'var(--bg)', overflow: 'hidden',
    }}>
      {/* Top bar — back link + breadcrumb */}
      <div style={{
        padding: '12px 28px', display: 'flex', alignItems: 'center', gap: 10,
        borderBottom: '1px solid var(--line-soft)',
      }}>
        <button
          type="button"
          onClick={() => navigate('/briefings')}
          className="btn-reset"
          style={{
            padding: '6px 10px', borderRadius: 6, cursor: 'pointer',
            color: 'var(--ink-mute)', fontSize: 12,
            display: 'inline-flex', alignItems: 'center', gap: 6,
          }}
        >
          ← Briefings
        </button>
        <div style={{ flex: 1 }} />
        <div style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--ink-mute)' }}>
          {briefing.id}
        </div>
      </div>

      {/* Header — title, status, owner, actions */}
      <div style={{
        padding: '20px 28px 18px', display: 'flex', flexDirection: 'column', gap: 14,
        borderBottom: '1px solid var(--line-soft)',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{
              fontFamily: 'var(--font-display)', fontSize: 24, fontWeight: 500,
              letterSpacing: '-0.01em', color: 'var(--ink)',
              overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
            }}>{briefing.title}</div>
            <div style={{ fontSize: 11, color: 'var(--ink-mute)', marginTop: 4 }}>
              Created {relTime(briefing.created_at)} · Updated {relTime(briefing.updated_at)}
            </div>
          </div>
          <StatusPill status={briefing.status} />
        </div>

        <div style={{ display: 'flex', alignItems: 'center', gap: 18, fontSize: 12, flexWrap: 'wrap' }}>
          <span style={{
            fontFamily: 'var(--font-mono)', fontSize: 9, letterSpacing: '.08em',
            color: 'var(--ink-mute)', textTransform: 'uppercase',
          }}>Owner</span>
          <OwnerInput
            value={briefing.owner || ''}
            onSubmit={(v) => patchBriefing(briefing.id, { owner: v })}
          />
          <div style={{ flex: 1 }} />
          <ActionButtons
            briefing={briefing}
            busy={busy}
            onTransition={handleTransition}
            onReject={() => setShowRejectModal(true)}
          />
        </div>
      </div>

      {/* Tabs */}
      <div style={{
        display: 'flex', gap: 4, padding: '0 28px',
        borderBottom: '1px solid var(--line-soft)',
      }}>
        {tabs.map(([id, label]) => (
          <button
            key={id}
            type="button"
            onClick={() => setTab(id)}
            className="btn-reset"
            style={{
              padding: '12px 12px', background: 'transparent', cursor: 'pointer', fontSize: 12,
              color: tab === id ? 'var(--ink)' : 'var(--ink-mute)',
              borderBottom: `2px solid ${tab === id ? 'var(--moss-deep)' : 'transparent'}`,
              marginBottom: -1,
            }}
          >{label}</button>
        ))}
      </div>

      {/* Body — full bleed */}
      <div style={{
        flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column',
        background: tab === 'intake' ? 'var(--bg-2)' : 'var(--bg)',
      }}>
        {tab === 'intake' && (
          <IntakePane
            briefingId={briefing.id}
            threadId={briefing.thread_id}
            onBriefingUpdated={() => loadBriefing && loadBriefing(briefing.id)}
          />
        )}
        {tab === 'brief' && (
          <div style={{ flex: 1, overflowY: 'auto', padding: '24px 28px' }}>
            <BriefTab brief={briefing.brief} />
          </div>
        )}
        {tab === 'cd_brief' && (
          <div style={{ flex: 1, overflowY: 'auto', padding: '24px 28px' }}>
            <CDBriefTab brief={briefing.cd_brief} />
          </div>
        )}
        {tab === 'comments' && (
          <div style={{ flex: 1, overflowY: 'auto', padding: '24px 28px' }}>
            <CommentsTab
              briefing={briefing}
              onSubmit={(body) => commentOnBriefing(briefing.id, body)}
            />
          </div>
        )}
        {tab === 'activity' && (
          <div style={{ flex: 1, overflowY: 'auto', padding: '24px 28px' }}>
            <ActivityTab events={briefing.activity || []} />
          </div>
        )}
      </div>

      {/* Reject modal */}
      {showRejectModal && (
        <RejectModal
          onCancel={() => setShowRejectModal(false)}
          onConfirm={async (reason) => {
            setShowRejectModal(false);
            await handleTransition('rejected', reason);
          }}
        />
      )}
    </div>
  );
}

// Wrapper that resolves the briefing record from the store before rendering
// the page. Keeps the app's route dispatcher trivial.
function BriefingDetailRoute() {
  const { route, briefingsById } = useStore();
  const briefing = briefingsById[route.briefingId];
  if (!briefing) {
    return (
      <div style={{
        flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center',
        background: 'var(--bg)', color: 'var(--ink-mute)', fontSize: 13,
      }}>Loading briefing…</div>
    );
  }
  return <BriefingDetailPage briefing={briefing} />;
}

function OwnerInput({ value, onSubmit }) {
  const [editing, setEditing] = useState(false);
  const [draft, setDraft] = useState(value);
  React.useEffect(() => { setDraft(value); }, [value]);
  if (!editing) {
    return (
      <button
        type="button"
        onClick={() => setEditing(true)}
        className="btn-reset"
        style={{
          padding: '4px 8px', borderRadius: 6,
          border: '1px solid var(--line-soft)',
          background: 'transparent', cursor: 'pointer',
          fontSize: 12, color: value ? 'var(--ink)' : 'var(--ink-mute)',
          fontStyle: value ? 'normal' : 'italic',
        }}
      >{value || 'Click to assign'}</button>
    );
  }
  return (
    <input
      autoFocus
      value={draft}
      onChange={(e) => setDraft(e.target.value)}
      onBlur={() => { setEditing(false); if (draft !== value) onSubmit(draft); }}
      onKeyDown={(e) => {
        if (e.key === 'Enter') { e.preventDefault(); e.target.blur(); }
        if (e.key === 'Escape') { setDraft(value); setEditing(false); }
      }}
      style={{
        padding: '4px 8px', borderRadius: 6,
        border: '1px solid var(--ink)',
        background: 'var(--bg)', color: 'var(--ink)',
        fontSize: 12, fontFamily: 'inherit', outline: 'none',
        minWidth: 200,
      }}
    />
  );
}

function ActionButtons({ briefing, busy, onTransition, onReject }) {
  const buttons = [];
  switch (briefing.status) {
    case 'draft':
      buttons.push({ label: 'Submit for creative review', to: 'in_creative_review', primary: true });
      buttons.push({ label: 'Archive', to: 'archived' });
      break;
    case 'in_creative_review':
      buttons.push({ label: 'Approve creative · pass to business', to: 'in_business_review', primary: true });
      buttons.push({ label: 'Reject', onClick: onReject, danger: true });
      break;
    case 'in_business_review':
      buttons.push({ label: 'Approve business · final approval', to: 'approved', primary: true });
      buttons.push({ label: 'Reject', onClick: onReject, danger: true });
      break;
    case 'approved':
      buttons.push({ label: 'Archive', to: 'archived' });
      break;
    case 'rejected':
      buttons.push({ label: 'Reopen as draft', to: 'draft' });
      buttons.push({ label: 'Archive', to: 'archived' });
      break;
    case 'archived':
      // Terminal — no actions
      break;
  }
  if (!buttons.length) return null;
  return (
    <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
      {buttons.map((b, i) => (
        <button
          key={i}
          type="button"
          disabled={busy}
          onClick={() => b.onClick ? b.onClick() : onTransition(b.to)}
          className="btn-reset"
          style={{
            padding: '7px 14px', borderRadius: 8,
            border: `1px solid ${b.danger ? '#e08a8a' : (b.primary ? 'var(--moss-deep)' : 'var(--line)')}`,
            background: b.primary ? 'var(--moss-deep)' : (b.danger ? '#fdecec' : 'var(--card-solid)'),
            color: b.primary ? 'var(--bg)' : (b.danger ? '#a72020' : 'var(--ink)'),
            fontSize: 12, cursor: busy ? 'default' : 'pointer',
            opacity: busy ? 0.6 : 1,
          }}
        >{b.label}</button>
      ))}
    </div>
  );
}

function RejectModal({ onCancel, onConfirm }) {
  const [reason, setReason] = useState('');
  return (
    <>
      <div
        onClick={onCancel}
        style={{
          position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.4)',
          zIndex: 200,
        }}
      />
      <div style={{
        position: 'fixed', top: '20%', left: '50%', transform: 'translateX(-50%)',
        width: 'min(480px, 92vw)',
        background: 'var(--bg)', borderRadius: 12,
        border: '1px solid var(--line)',
        boxShadow: '0 20px 50px rgba(0,0,0,.2)',
        zIndex: 201,
        padding: 22,
        display: 'flex', flexDirection: 'column', gap: 14,
      }}>
        <div style={{ fontFamily: 'var(--font-display)', fontSize: 18 }}>Reject briefing</div>
        <div style={{ fontSize: 12, color: 'var(--ink-soft)' }}>
          Add a short reason. The briefing can be reopened as draft.
        </div>
        <textarea
          autoFocus
          value={reason}
          onChange={(e) => setReason(e.target.value)}
          placeholder="What's wrong with this brief?"
          rows={4}
          style={{
            padding: '8px 12px', borderRadius: 8,
            border: '1px solid var(--line)',
            fontFamily: 'inherit', fontSize: 13,
            resize: 'vertical', outline: 'none',
          }}
        />
        <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
          <button
            type="button"
            onClick={onCancel}
            className="btn-reset"
            style={{
              padding: '7px 14px', borderRadius: 8,
              border: '1px solid var(--line)',
              background: 'var(--card-solid)', color: 'var(--ink)',
              fontSize: 12, cursor: 'pointer',
            }}
          >Cancel</button>
          <button
            type="button"
            onClick={() => onConfirm(reason)}
            disabled={!reason.trim()}
            className="btn-reset"
            style={{
              padding: '7px 14px', borderRadius: 8,
              border: '1px solid #e08a8a',
              background: reason.trim() ? '#fdecec' : 'var(--bg-2)',
              color: '#a72020',
              fontSize: 12, cursor: reason.trim() ? 'pointer' : 'default',
              opacity: reason.trim() ? 1 : 0.6,
            }}
          >Reject</button>
        </div>
      </div>
    </>
  );
}

// ─── Tab content ─────────────────────────────────────────────────────────

function BriefTab({ brief }) {
  if (!brief) return <Empty text="No marketing brief captured yet." />;
  // Reuse the existing BriefCard component.
  return <BriefCard brief={brief} onOpen={() => {}} />;
}

function CDBriefTab({ brief }) {
  if (!brief) return <Empty text="No creative-direction brief captured yet." />;
  return <CDBriefCard brief={brief} />;
}

function CommentsTab({ briefing, onSubmit }) {
  const [body, setBody] = useState('');
  const [busy, setBusy] = useState(false);

  const submit = async () => {
    if (!body.trim() || busy) return;
    setBusy(true);
    try {
      await onSubmit(body);
      setBody('');
    } finally { setBusy(false); }
  };

  const comments = briefing.comments || [];

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
      <div>
        <textarea
          value={body}
          onChange={(e) => setBody(e.target.value)}
          placeholder="Leave a comment…"
          rows={3}
          style={{
            width: '100%', padding: '10px 12px', borderRadius: 8,
            border: '1px solid var(--line)', background: 'var(--card-solid)',
            fontFamily: 'inherit', fontSize: 13,
            resize: 'vertical', outline: 'none', boxSizing: 'border-box',
          }}
        />
        <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 8 }}>
          <button
            type="button"
            onClick={submit}
            disabled={!body.trim() || busy}
            className="btn-reset"
            style={{
              padding: '7px 14px', borderRadius: 8,
              border: '1px solid var(--moss-deep)',
              background: body.trim() ? 'var(--moss-deep)' : 'var(--bg-2)',
              color: body.trim() ? 'var(--bg)' : 'var(--ink-mute)',
              fontSize: 12, cursor: body.trim() && !busy ? 'pointer' : 'default',
              opacity: busy ? 0.6 : 1,
            }}
          >{busy ? 'Posting…' : 'Post comment'}</button>
        </div>
      </div>
      {comments.length === 0 ? (
        <div style={{ fontSize: 12, color: 'var(--ink-mute)', fontStyle: 'italic' }}>
          No comments yet.
        </div>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          {comments.slice().reverse().map(c => (
            <div key={c.id} style={{
              padding: 12, borderRadius: 8,
              background: 'var(--bg-2)', border: '1px solid var(--line-soft)',
            }}>
              <div style={{
                display: 'flex', gap: 8, alignItems: 'baseline',
                fontSize: 11, marginBottom: 4,
              }}>
                <span style={{ fontWeight: 500, color: 'var(--ink)' }}>{c.author}</span>
                <span style={{ color: 'var(--ink-mute)', fontFamily: 'var(--font-mono)', fontSize: 10 }}>
                  {relTime(c.created_at)}
                </span>
              </div>
              <div style={{ fontSize: 13, lineHeight: 1.5, color: 'var(--ink)', whiteSpace: 'pre-wrap' }}>
                {c.body}
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function ActivityTab({ events }) {
  if (!events.length) return <Empty text="No activity yet." />;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {events.slice().reverse().map((e, i) => (
        <div key={i} style={{
          padding: '8px 12px', borderRadius: 6,
          background: 'var(--card-solid)', border: '1px solid var(--line-soft)',
          fontSize: 12, lineHeight: 1.5,
        }}>
          <div style={{
            display: 'flex', gap: 8, fontFamily: 'var(--font-mono)',
            fontSize: 9, letterSpacing: '.06em', color: 'var(--ink-mute)',
            textTransform: 'uppercase',
          }}>
            <span>{e.kind.replace(/_/g, ' ')}</span>
            <span style={{ flex: 1 }} />
            <span>{relTime(e.at)} · {e.actor}</span>
          </div>
          {e.detail && Object.keys(e.detail).length > 0 && (
            <div style={{ marginTop: 4, color: 'var(--ink-soft)' }}>
              {Object.entries(e.detail).map(([k, v]) => `${k}: ${v}`).join(' · ')}
            </div>
          )}
        </div>
      ))}
    </div>
  );
}

function Empty({ text }) {
  return (
    <div style={{
      padding: '40px 0', textAlign: 'center',
      color: 'var(--ink-mute)', fontSize: 13, fontStyle: 'italic',
    }}>{text}</div>
  );
}

// ─── IntakePane (full-page CD chat) ─────────────────────────────────────
//
// Lives inside BriefingDetailPage when status is `draft` and no marketing
// brief has landed yet. Owns its own streaming state — calls
// api.chat({threadId, userText, onEvent}) and accumulates text + activity
// locally so we don't have to navigate to /t/<id>. When the backend
// emits `briefing_record` (or any artifact that fills the brief), we
// trigger a re-fetch of the briefing record so the parent page flips
// to the review tabs.
//
// Why local (not store) streaming state: the store's streaming slot is
// keyed to route.threadId, but the intake runs while the route is on
// /briefings/<id>. Keeping it local avoids tangling the two flows.

function IntakePane({ threadId, briefingId, onBriefingUpdated }) {
  const { loadBriefings } = useStore();

  const [input, setInput] = useState('');
  const [streaming, setStreaming] = useState(false);
  const [partial, setPartial] = useState('');
  const [history, setHistory] = useState([]);   // [{role:'user'|'assistant', text}]
  const [activity, setActivity] = useState([]); // structured cards (pickers, brief, etc.)
  const [todos, setTodos] = useState([]);
  const scrollRef = React.useRef(null);

  // Load the thread's persisted messages so the whole conversation —
  // prose AND the agent activity trail (tool calls, pickers, todos) —
  // survives page reloads. `api.appendMessage` already stores
  // {activity, todos, steps, plan} alongside each assistant message
  // (see api.js:535), so the rehydrate just keeps those fields.
  useEffect(() => {
    if (!threadId) return;
    let cancelled = false;
    setInput('');
    setStreaming(false);
    setPartial('');
    setActivity([]);
    setTodos([]);
    (async () => {
      try {
        const msgs = await api.listMessages(threadId);
        if (cancelled) return;
        const restored = (msgs || [])
          .filter(m => m.role === 'user' || m.role === 'assistant')
          .map(m => {
            const text = typeof m.content === 'string'
              ? m.content
              : (m.content?.[0]?.text || '');
            return {
              role: m.role,
              text,
              // Carry the agent receipts forward so the historical turn
              // renders with the same trail it had when it streamed.
              activity: Array.isArray(m.activity) ? m.activity : [],
              todos: Array.isArray(m.todos) ? m.todos : [],
            };
          });
        setHistory(restored);
      } catch (e) {
        console.error('[intake] failed to load history', e);
        setHistory([]);
      }
    })();
    return () => { cancelled = true; };
  }, [threadId]);

  // Keep the conversation pinned to the bottom as text streams in.
  useEffect(() => {
    if (scrollRef.current) {
      scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
    }
  }, [partial, history.length, activity.length, todos.length]);

  const send = async () => {
    const text = input.trim();
    if (!text || streaming || !threadId) return;
    setInput('');
    setHistory(h => [...h, { role: 'user', text }]);
    setStreaming(true);
    setPartial('');

    try {
      // Persist the user turn — api.chat reads prior history from
      // localStorage to build the SSE request body, and the IntakePane
      // mount-effect re-hydrates from this same store on next visit.
      await api.appendMessage(threadId, { role: 'user', content: text });

      await api.chat({
        threadId,
        userText: text,
        attachments: [],
        onEvent: (ev) => {
          if (ev.type === 'content.delta') {
            setPartial(ev.text);
          } else if (ev.type === 'todos') {
            setTodos(ev.todos || []);
          } else if (ev.type === 'activity') {
            // Capture every activity entry — tools, subagents, and
            // artifacts (pickers, briefs). The ActivityTrail component
            // renders them with full transparency: tool name, args,
            // result preview, subagent boundaries.
            if (ev.entry) setActivity(a => [...a, ev.entry]);
          } else if (ev.type === 'activity.update') {
            // Hydrates a tool entry with its result body once the
            // backend emits tool.result. Patch in place by tool_use_id.
            setActivity(a => a.map(e =>
              (e.tool_use_id && ev.entry && e.tool_use_id === ev.entry.tool_use_id)
                ? ev.entry
                : e
            ));
          } else if (ev.type === 'briefing_record' || ev.type === 'briefing.record') {
            // The backend either created or updated a record. Either way
            // pull a fresh copy so the parent page can flip from intake
            // to review tabs.
            onBriefingUpdated && onBriefingUpdated();
          }
        },
      });
    } catch (err) {
      console.error('[briefing-intake] stream failed', err);
      setActivity(a => [...a, { kind: 'error', text: String(err.message || err) }]);
    } finally {
      // Commit the assistant turn — text PLUS its agent receipts
      // (activity trail and todos) — as a frozen entry in history.
      // `api.chat` also persists the same data to localStorage, so
      // re-mount picks it back up as a frozen turn next time.
      const turnActivity = activity;
      const turnTodos = todos;
      setHistory(h => partial
        ? [...h, { role: 'assistant', text: partial, activity: turnActivity, todos: turnTodos }]
        : h
      );
      setPartial('');
      // Reset the live trail — next turn starts fresh.
      setActivity([]);
      setTodos([]);
      setStreaming(false);
      // Refresh both list and detail — SSE may have updated either.
      loadBriefings && loadBriefings();
      onBriefingUpdated && onBriefingUpdated();
    }
  };

  return (
    <div style={{
      flex: 1, display: 'flex', flexDirection: 'column',
      overflow: 'hidden', background: 'var(--bg-2)',
    }}>
      {/* Conversation area */}
      <div ref={scrollRef} style={{
        flex: 1, overflowY: 'auto', padding: '24px 32px',
        display: 'flex', flexDirection: 'column', gap: 14,
      }}>
        <div style={{
          maxWidth: 720, width: '100%', margin: '0 auto',
          display: 'flex', flexDirection: 'column', gap: 14,
        }}>
          {history.length === 0 && !streaming && activity.length === 0 && (
            <div style={{
              padding: '32px 0', color: 'var(--ink-mute)',
              fontSize: 13.5, fontStyle: 'italic', textAlign: 'center',
            }}>
              Tell the Creative Director what you want to brief — product, market,
              audience cue, anything you have. The agent runs the intake from there.
            </div>
          )}

          {history.map((m, i) => (
            <React.Fragment key={i}>
              <div
                style={{
                  alignSelf: m.role === 'user' ? 'flex-end' : 'flex-start',
                  maxWidth: '88%',
                  padding: '10px 14px', borderRadius: 12,
                  background: m.role === 'user' ? 'var(--moss-deep)' : 'var(--card-solid)',
                  color: m.role === 'user' ? 'var(--bg)' : 'var(--ink)',
                  border: m.role === 'user' ? 'none' : '1px solid var(--line)',
                  fontSize: 13.5, lineHeight: 1.5, whiteSpace: 'pre-wrap',
                }}
              >
                {m.text}
              </div>
              {/* Persisted agent receipts for this turn — render the
                  todos and activity trail right under the assistant
                  bubble, so the user sees what was run when they
                  reload the page or come back to the briefing. */}
              {m.role === 'assistant' && Array.isArray(m.todos) && m.todos.length > 0 && window.TodoListCard && (
                <window.TodoListCard todos={m.todos} />
              )}
              {m.role === 'assistant' && Array.isArray(m.activity) && m.activity.length > 0 && window.ActivityTrail && (
                <window.ActivityTrail activity={m.activity.filter(e => e.kind !== 'error')} />
              )}
            </React.Fragment>
          ))}

          {/* Status pill — surfaced the moment the user hits Send and
              stays visible until either tool calls start landing or the
              first text chunk streams in. Closes the "did anything
              happen?" gap on slow first-byte turns. */}
          {streaming && !partial && activity.length === 0 && todos.length === 0 && (
            <div style={{
              alignSelf: 'flex-start', maxWidth: '88%',
              padding: '8px 14px', borderRadius: 12,
              background: 'var(--bg-2)', border: '1px solid var(--line)',
              color: 'var(--ink-mute)', fontSize: 12.5, fontStyle: 'italic',
              display: 'inline-flex', alignItems: 'center', gap: 8,
            }}>
              <span className="pulse">●</span>
              Creative Director is thinking…
            </div>
          )}

          {streaming && partial && (
            <div style={{
              alignSelf: 'flex-start', maxWidth: '88%',
              padding: '10px 14px', borderRadius: 12,
              background: 'var(--card-solid)', border: '1px solid var(--line)',
              color: 'var(--ink)', fontSize: 13.5, lineHeight: 1.5,
              whiteSpace: 'pre-wrap',
            }}>
              {partial}
              <span className="pulse" style={{ marginLeft: 4 }}>▍</span>
            </div>
          )}

          {/* Live trail — only rendered for the IN-FLIGHT turn. Frozen
              into the assistant message above on stream end. */}
          {streaming && todos.length > 0 && window.TodoListCard && (
            <window.TodoListCard todos={todos} />
          )}

          {/* Errors get a small inline pill — surfaced alongside the trail */}
          {activity.filter(e => e.kind === 'error').map((e, i) => (
            <div key={`err-${i}`} style={{
              padding: '10px 12px', borderRadius: 8,
              background: '#fdecec', color: '#a72020',
              border: '1px solid #e08a8a', fontSize: 12,
            }}>{e.text}</div>
          ))}

          {streaming && activity.length > 0 && window.ActivityTrail && (
            <window.ActivityTrail activity={activity.filter(e => e.kind !== 'error')} />
          )}
        </div>
      </div>

      {/* Composer — pinned to bottom of pane */}
      <div style={{
        padding: '14px 32px 18px', borderTop: '1px solid var(--line-soft)',
        background: 'var(--bg)',
      }}>
        <div style={{
          maxWidth: 720, margin: '0 auto',
          display: 'flex', gap: 10, alignItems: 'flex-end',
          padding: 12, borderRadius: 12,
          border: '1px solid var(--line)', background: 'var(--card-solid)',
        }}>
          <textarea
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); }
            }}
            placeholder={streaming ? 'Agent is responding…' : 'Describe the brief — product, market, audience…'}
            disabled={streaming}
            rows={2}
            style={{
              flex: 1, border: 'none', outline: 'none', resize: 'none',
              background: 'transparent',
              fontFamily: 'var(--font-serif)', fontSize: 14,
              color: 'var(--ink)', minHeight: 32, lineHeight: 1.4,
            }}
          />
          <button
            type="button"
            onClick={send}
            disabled={streaming || !input.trim()}
            className="btn-reset"
            style={{
              padding: '8px 12px', borderRadius: 8,
              background: input.trim() && !streaming ? 'var(--moss-deep)' : 'var(--bg-2)',
              color: input.trim() && !streaming ? 'var(--bg)' : 'var(--ink-mute)',
              cursor: input.trim() && !streaming ? 'pointer' : 'default',
              fontSize: 12, display: 'flex', gap: 6, alignItems: 'center',
            }}
          >
            Send <Icon name="send" size={13} />
          </button>
        </div>
      </div>
    </div>
  );
}

window.BriefingsView = BriefingsView;
window.BriefingDetailRoute = BriefingDetailRoute;
window.StatusPill = StatusPill;
