/* global React */
/* =============================================================================
   MOMENTUM LOG  v5.0  — Practice-Centric Momentum log Phase 1.1
   Three Day Cell states · Daily Detail Modal · Registry View · Discovery Engine
   ========================================================================== */

const { useState, useMemo, useRef, useEffect, useCallback } = React;

// ── Domain map ────────────────────────────────────────────────────────────────
const DOMAIN_CLASS = {
  physical:      'dom-physical',
  inner:         'dom-inner',
  creative:      'dom-creative',
  interpersonal: 'dom-interpersonal',
  adventure:     'dom-adventure',
  admin:         'dom-admin',
};

const DOMAIN_LABEL = {
  physical:      'Physical Form',
  inner:         'Inner World',
  creative:      'Creative',
  interpersonal: 'Interpersonal',
  adventure:     'Adventure',
  admin:         'Life Admin',
};

const AVAILABLE_ICONS = [
  'bell','bond','book','cross','dance','fire-1','hold-book','hold-cloud',
  'hold-eye','hold-globe','hold-question','leaf','manuscripts','moon',
  'red-light','star-maps','support',
];

const _iconHref = (ic) =>
  `/dashboard-ui/assets/icons/${AVAILABLE_ICONS.includes(ic) ? ic : 'star-maps'}.svg`;

// ── Date helpers ──────────────────────────────────────────────────────────────
const DAY_NAMES   = ['SUN','MON','TUE','WED','THU','FRI','SAT'];
const MONTH_NAMES = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
const today       = () => new Date();
const dateKey     = (d) => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
const fmtInputDate = (d) => {
  const m = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  return `${d.getFullYear()}-${m}-${day}`;
};
const fmtDisplayDate = (isoStr) => {
  const d = new Date(isoStr + 'T00:00:00');
  return `${MONTH_NAMES[d.getMonth()]} ${d.getDate()}`;
};
const fmtTime = (isoStr) => {
  if (!isoStr) return '';
  const d = new Date(isoStr);
  return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
};

const momentumDays = () => {
  const t = today();
  const out = [];
  for (let i = 6; i >= 0; i--) {
    const d = new Date(t);
    d.setDate(t.getDate() - i);
    out.push(d);
  }
  return out;
};

const calendarDays = () => {
  const t = today();
  const todayDay = t.getDay();
  const currentMonday = new Date(t);
  currentMonday.setDate(t.getDate() - ((todayDay + 6) % 7));
  const startMonday = new Date(currentMonday);
  startMonday.setDate(currentMonday.getDate() - 21);
  return Array.from({ length: 28 }, (_, i) => {
    const d = new Date(startMonday);
    d.setDate(startMonday.getDate() + i);
    return d;
  });
};

// ── Icon component (WCAG: accessible tooltip on hover/focus) ─────────────────
const PracticeIcon = ({ icon, domain, name, size = 28, onClick }) => {
  const cls  = DOMAIN_CLASS[domain] || 'dom-admin';
  const url  = `url(${_iconHref(icon)})`;
  return (
    <button
      className={`pico ${cls}`}
      style={{
        width: size, height: size, background: 'none', border: 'none',
        padding: 0, cursor: onClick ? 'pointer' : 'default', position: 'relative',
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
      }}
      title={name}
      aria-label={name}
      onClick={onClick}
    >
      <span style={{
        display: 'block', width: size, height: size,
        maskImage: url, WebkitMaskImage: url,
        maskSize: 'contain', maskRepeat: 'no-repeat', maskPosition: 'center',
        WebkitMaskSize: 'contain', WebkitMaskRepeat: 'no-repeat', WebkitMaskPosition: 'center',
        backgroundColor: 'var(--d-color, #888)',
      }} />
    </button>
  );
};

// ── UndoToast ─────────────────────────────────────────────────────────────────
const UndoToast = ({ message, onUndo, onDismiss }) => {
  const [visible, setVisible] = useState(true);
  const timerRef = useRef(null);

  useEffect(() => {
    timerRef.current = setTimeout(() => { setVisible(false); onDismiss(); }, 5000);
    return () => clearTimeout(timerRef.current);
  }, []);

  useEffect(() => {
    const handler = (e) => {
      if (e.key === 'Escape') { setVisible(false); onDismiss(); }
    };
    document.addEventListener('keydown', handler);
    return () => document.removeEventListener('keydown', handler);
  }, []);

  if (!visible) return null;
  return (
    <div
      role="status"
      aria-live="assertive"
      aria-atomic="true"
      style={{
        position: 'fixed', bottom: 24, left: '50%', transform: 'translateX(-50%)',
        background: 'var(--stone-1, #1c1c1c)', color: 'var(--ink-1, #fff)',
        borderRadius: 8, padding: '10px 18px', display: 'flex', alignItems: 'center',
        gap: 12, fontSize: 13, zIndex: 900, boxShadow: '0 4px 24px rgba(0,0,0,0.4)',
        fontFamily: 'var(--font-mono, monospace)',
      }}
    >
      <span>{message}</span>
      <button
        onClick={() => { clearTimeout(timerRef.current); onUndo(); setVisible(false); }}
        style={{
          background: 'var(--gold-1, #bb9863)', color: '#fff', border: 'none',
          borderRadius: 4, padding: '3px 10px', cursor: 'pointer', fontSize: 12,
          fontWeight: 600,
        }}
      >
        Undo
      </button>
      <button
        onClick={() => { clearTimeout(timerRef.current); setVisible(false); onDismiss(); }}
        aria-label="Dismiss"
        style={{ background: 'none', border: 'none', color: 'var(--ink-3,#888)', cursor: 'pointer', fontSize: 16, padding: '0 2px' }}
      >
        ×
      </button>
    </div>
  );
};

// ── Day Cell ──────────────────────────────────────────────────────────────────
// Three mutually exclusive states: has_entries | empty | no_practices
const DayCell = ({ date, entries, isToday, hasPractices, onAdd, onCellClick }) => {
  const [longPress, setLongPress] = useState(false);
  const lpTimer = useRef(null);

  const handleTouchStart = () => {
    lpTimer.current = setTimeout(() => { setLongPress(true); onAdd(date); }, 500);
  };
  const handleTouchEnd = () => { clearTimeout(lpTimer.current); setLongPress(false); };

  const todayStr = dateKey(today());
  const cellDate = dateKey(date);


  // State: future (calendar days after today — inert)
  const isFuture = cellDate > todayStr;
  if (isFuture) {
    return (
      <div
        className="cell cell--empty is-future"
        aria-disabled="true"
        tabIndex={isFuture ? -1 : 0}
      >
        <div className="cell__head">
          <span className="cell__day">{DAY_NAMES[date.getDay()]}</span>
          <span className="cell__date">{date.getDate()}</span>
        </div>
        <span className="cell__future-label">Future</span>
      </div>
    );
  }
  // State: no_practices (zero practices registered anywhere)
  if (!hasPractices) {
    return (
      <div className={`cell cell--no-practices${isToday ? ' is-today' : ''}`}>
        <div className="cell__head">
          <span className="cell__day">{DAY_NAMES[date.getDay()]}</span>
          <span className="cell__date">{date.getDate()}</span>
        </div>
        <div className="cell--no-practices__body">
          <span className="cell--no-practices__hint">—</span>
        </div>
      </div>
    );
  }

  // State: has_entries (≥1 Practice Log Entry)
  if (entries.length > 0) {
    return (
      <div
        className={`cell cell--has-entries${isToday ? ' is-today' : ''}`}
        style={{ cursor: 'pointer' }}
        onClick={() => onCellClick(date, entries)}
        onKeyDown={(e) => e.key === 'Enter' && onCellClick(date, entries)}
        tabIndex={0}
        role="button"
        aria-label={`${fmtDisplayDate(cellDate)}: ${entries.length} practice${entries.length > 1 ? 's' : ''} logged`}
      >
        <div className="cell__head">
          <span className="cell__day">{DAY_NAMES[date.getDay()]}</span>
          <span className="cell__date">{date.getDate()}</span>
          {isToday && <span className="cell__flag">today</span>}
        </div>
        <div className="cell__cluster" style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 8 }}>
          {entries.map((e, i) => (
            <PracticeIcon
              key={e.practice_id || i}
              icon={e.icon}
              domain={e.domain}
              name={e.name}
              size={26}
              onClick={(ev) => { ev.stopPropagation(); onCellClick(date, [e]); }}
            />
          ))}
        </div>
        <div className="cell__foot">
          <button
            className="cell__add"
            aria-label={`Log practice for ${fmtDisplayDate(cellDate)}`}
            onClick={(ev) => { ev.stopPropagation(); onAdd(date); }}
          >+</button>
        </div>
      </div>
    );
  }

  // State: empty (Reflective Gap — practices exist, none logged for this date)
  return (
    <div
      className={`cell cell--empty is-uncharted${isToday ? ' is-today' : ''}`}
      onTouchStart={handleTouchStart}
      onTouchEnd={handleTouchEnd}
      onClick={() => onAdd(date)}
      style={{ cursor: 'pointer' }}
    >
      <div className="cell__head">
        <span className="cell__day">{DAY_NAMES[date.getDay()]}</span>
        <span className="cell__date">{date.getDate()}</span>
        {isToday && <span className="cell__flag">today</span>}
      </div>
      <div className="cell__cluster" />
      <div className="cell__foot">
        <button
          className="cell__add"
          aria-label={`Log practice for ${fmtDisplayDate(cellDate)}`}
          onClick={() => onAdd(date)}
        >+</button>
      </div>
    </div>
  );
};

// ── First-run Onboarding Tile ─────────────────────────────────────────────────
const FirstRunTile = ({ onAdd }) => (
  <article className="firstrun-tile" style={{
    gridColumn: '1 / -1', display: 'flex', flexDirection: 'column',
    alignItems: 'center', justifyContent: 'center', padding: '40px 24px',
    border: '1px dashed var(--stone-3, #3a3a3a)', borderRadius: 12,
    textAlign: 'center', gap: 12,
  }}>
    <p style={{ margin: 0, fontSize: 15, color: 'var(--ink-2, #ccc)', maxWidth: 360 }}>
      Track what you practice. Tap + to log your first Practice.
    </p>
    <p style={{ margin: 0, fontSize: 12, color: 'var(--ink-4, #666)', maxWidth: 360 }}>
      Practices are repeated activities — sauna, meditation, writing. Different from tasks.
    </p>
    <button className="btn btn--primary" onClick={() => onAdd(today())}>+ Log your first Practice</button>
  </article>
);

// ── Suggestion Card (Discovery Engine) ───────────────────────────────────────
const SuggestionCard = ({ suggestion, onPromote, onIgnore, onSnooze }) => {
  const cls = DOMAIN_CLASS[suggestion.domain] || 'dom-admin';
  return (
    <div className={`suggest-card ${cls}`} role="region" aria-label="Practice suggestion" style={{
      background: 'var(--stone-1, #1e1e1e)',
      border: '1px solid var(--d-color, var(--stone-3))',
      borderRadius: 10, padding: '14px 18px', marginBottom: 12,
      display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap',
    }}>
      <div style={{ flex: 1, minWidth: 200 }}>
        <span className="label-caps" style={{ fontSize: 10, color: 'var(--gold-2, #bb9863)' }}>
          Discovery Engine · Pattern Detected
        </span>
        <p style={{ margin: '4px 0 2px', fontWeight: 600, color: 'var(--ink-1, #fff)', fontSize: 14 }}>
          {suggestion.candidate_name}
        </p>
        <p style={{ margin: 0, fontSize: 12, color: 'var(--ink-3, #888)' }}>
          Logged {suggestion.frequency_count}× this week
        </p>
      </div>
      <div style={{ display: 'flex', gap: 8 }}>
        <button className="btn btn--primary" onClick={onPromote} style={{ fontSize: 12, padding: '5px 12px' }}>
          Promote
        </button>
        <button className="btn" onClick={onSnooze} style={{ fontSize: 12, padding: '5px 12px' }}>
          Snooze
        </button>
        <button className="btn" onClick={onIgnore} style={{ fontSize: 12, padding: '5px 12px', opacity: 0.7 }}>
          Ignore
        </button>
      </div>
    </div>
  );
};

// ── Log Entry Form (no notes — PRD §3.3) ─────────────────────────────────────
const LogEntryForm = ({ initialDate, registry, onClose, onSave }) => {
  const [dateStr,     setDateStr]     = useState(fmtInputDate(initialDate || today()));
  const [query,       setQuery]       = useState('');
  const [selectedId,  setSelectedId]  = useState(null);
  const [open,        setOpen]        = useState(false);
  const backdropRef = useRef(null);
  const inputRef    = useRef(null);

  useEffect(() => { requestAnimationFrame(() => setOpen(true)); }, []);
  useEffect(() => {
    if (inputRef.current) inputRef.current.focus();
    document.body.style.overflow = 'hidden';
    return () => { document.body.style.overflow = ''; };
  }, []);

  const handleClose = () => { setOpen(false); setTimeout(onClose, 280); };
  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') handleClose(); };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, []);

  const filtered = useMemo(() => {
    const q = query.trim().toLowerCase();
    if (!q) return registry;
    return registry.filter((p) =>
      p.name.toLowerCase().includes(q) ||
      (DOMAIN_LABEL[p.domain] || '').toLowerCase().includes(q)
    );
  }, [query, registry]);

  const selected = registry.find((p) => p.id === selectedId);

  return (
    <div
      ref={backdropRef}
      className={`drawer-backdrop${open ? ' is-open' : ''}`}
      onMouseDown={(e) => { if (e.target === backdropRef.current) handleClose(); }}
    >
      <div className={`drawer${open ? ' is-open' : ''}`}>
        <button className="drawer__close" onClick={handleClose} aria-label="Close">×</button>
        <header className="drawer__head">
          <span className="label-caps drawer__eye">New Entry</span>
          <h3 className="drawer__title">Log a practice.</h3>
        </header>
        <div className="drawer__body">
          <div className="entry__field">
            <label className="entry__label">Date</label>
            <input
              type="date"
              className="entry__input"
              value={dateStr}
              max={fmtInputDate(today())}
              onChange={(e) => setDateStr(e.target.value)}
            />
          </div>
          <div className="entry__field">
            <label className="entry__label">Practice</label>
            <input
              ref={inputRef}
              className="entry__input"
              placeholder="Search — sauna, meditation…"
              value={query}
              onChange={(e) => setQuery(e.target.value)}
            />
            <div className="entry__list">
              {filtered.length === 0 && <div className="entry__empty">No matches.</div>}
              {filtered.map((p) => {
                const cls = DOMAIN_CLASS[p.domain] || 'dom-admin';
                return (
                  <div
                    key={p.id}
                    className={`entry__row ${cls}${selectedId === p.id ? ' is-selected' : ''}`}
                    onClick={() => setSelectedId(p.id)}
                    tabIndex={0}
                    role="option"
                    aria-selected={selectedId === p.id}
                    onKeyDown={(e) => e.key === 'Enter' && setSelectedId(p.id)}
                  >
                    <span className="entry__row-node" style={{ '--ico': `url(${_iconHref(p.icon)})` }}>
                      <span className="entry__row-node-ico" style={{
                        maskImage: `url(${_iconHref(p.icon)})`,
                        WebkitMaskImage: `url(${_iconHref(p.icon)})`,
                      }} />
                    </span>
                    <span className="entry__row-label">{p.name}</span>
                    <span className="entry__row-dom">{DOMAIN_LABEL[p.domain] || p.domain}</span>
                  </div>
                );
              })}
            </div>
          </div>
        </div>
        <footer className="drawer__foot">
          <button
            className="btn btn--primary"
            disabled={!selected}
            style={!selected ? { opacity: 0.4, cursor: 'not-allowed' } : null}
            onClick={() => selected && onSave({ practice_id: selected.id, date: dateStr, source: 'manual' })}
          >
            Log Practice
          </button>
        </footer>
      </div>
    </div>
  );
};

// ── Daily Detail Modal ────────────────────────────────────────────────────────
const DailyDetailModal = ({ practiceId, entryDate, onClose, onMoved, registry }) => {
  const [detail,    setDetail]    = useState(null);
  const [editMode,  setEditMode]  = useState(false);
  const [editName,  setEditName]  = useState('');
  const [moveDateStr, setMoveDateStr] = useState('');
  const [undoToast, setUndoToast] = useState(null); // { message, entryId, oldDate }
  const [open,      setOpen]      = useState(false);
  const backdropRef = useRef(null);

  const stripDays = momentumDays().map(d => fmtInputDate(d));
  const todayStr  = fmtInputDate(today());

  useEffect(() => { requestAnimationFrame(() => setOpen(true)); }, []);
  useEffect(() => {
    document.body.style.overflow = 'hidden';
    return () => { document.body.style.overflow = ''; };
  }, []);

  useEffect(() => {
    fetch(`/api/momentum/detail/${practiceId}/${entryDate}`)
      .then((r) => r.ok ? r.json() : null)
      .then((d) => {
        if (d) {
          setDetail(d);
          setEditName(d.name);
          setMoveDateStr(entryDate);
        }
      })
      .catch(() => {});
  }, [practiceId, entryDate]);

  const handleClose = () => { setOpen(false); setTimeout(onClose, 280); };
  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') handleClose(); };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, []);

  const handleSaveName = () => {
    if (!editName.trim() || editName.trim() === detail.name) return;
    fetch(`/api/momentum/registry/${practiceId}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name: editName.trim() }),
    }).then(() => setDetail((d) => ({ ...d, name: editName.trim() }))).catch(() => {});
  };

  const handleMove = (entryId) => {
    if (!moveDateStr || moveDateStr === entryDate) return;
    const originalDate = entryDate;
    fetch(`/api/momentum/log/${entryId}/move`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ date: moveDateStr }),
    })
      .then((r) => r.ok ? r.json() : null)
      .then((res) => {
        if (!res) return;
        setUndoToast({
          message: `Moved to ${fmtDisplayDate(moveDateStr)}`,
          entryId,
          oldDate: originalDate,
          newDate: moveDateStr,
        });
        setDetail((d) => ({ ...d, date: moveDateStr }));
        if (onMoved) onMoved();
      })
      .catch(() => {});
  };

  const handleUndo = () => {
    if (!undoToast) return;
    fetch(`/api/momentum/log/${undoToast.entryId}/move`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ date: undoToast.oldDate }),
    }).then(() => {
      setDetail((d) => ({ ...d, date: undoToast.oldDate }));
      if (onMoved) onMoved();
    }).catch(() => {});
    setUndoToast(null);
  };

  const cls = detail ? (DOMAIN_CLASS[detail.domain] || 'dom-admin') : '';

  return (
    <React.Fragment>
      <div
        ref={backdropRef}
        className={`drawer-backdrop${open ? ' is-open' : ''}`}
        onMouseDown={(e) => { if (e.target === backdropRef.current) handleClose(); }}
      >
        <div className={`drawer${open ? ' is-open' : ''}`}>
          <button className="drawer__close" onClick={handleClose} aria-label="Close modal">×</button>
          {!detail && (
            <div className="drawer__body" style={{ color: 'var(--ink-3)', padding: '40px 24px', textAlign: 'center' }}>
              Loading…
            </div>
          )}
          {detail && (
            <React.Fragment>
              <header className={`drawer__head ${cls}`} style={{ paddingBottom: 12 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 6 }}>
                  <PracticeIcon icon={detail.icon} domain={detail.domain} name={detail.name} size={30} />
                  {!editMode ? (
                    <h3 className="drawer__title" style={{ margin: 0 }}>{detail.name}</h3>
                  ) : (
                    <input
                      className="entry__input"
                      value={editName}
                      onChange={(e) => setEditName(e.target.value)}
                      onBlur={handleSaveName}
                      maxLength={60}
                      style={{ fontSize: 20, fontFamily: 'var(--font-serif-display)', background: 'transparent', border: 'none', borderBottom: '1px solid var(--stone-3)', color: 'var(--ink-1)', width: '100%' }}
                      aria-label="Edit practice name"
                    />
                  )}
                </div>
                <span className={`drawer__domain-tag ${cls}`} style={{ fontSize: 11 }}>
                  {DOMAIN_LABEL[detail.domain] || detail.domain}
                </span>
                <span style={{ fontSize: 11, color: 'var(--ink-3)', marginLeft: 8 }}>
                  {fmtDisplayDate(entryDate)}
                </span>
                {detail.entries.length > 1 && (
                  <span style={{ fontSize: 11, color: 'var(--gold-2, #bb9863)', marginLeft: 8, fontWeight: 600 }}>
                    Logged {detail.entries.length}× this day
                  </span>
                )}
                <button
                  className="btn"
                  onClick={() => setEditMode((v) => !v)}
                  style={{ marginLeft: 'auto', display: 'block', marginTop: 8, fontSize: 11 }}
                  aria-pressed={editMode}
                >
                  {editMode ? 'Done' : 'Edit'}
                </button>
              </header>

              {/* Edit Mode: Move to Date */}
              {editMode && detail.entries.length > 0 && (
                <div style={{ padding: '12px 24px', borderBottom: '1px solid var(--stone-3)' }}>
                  <label className="entry__label">Move to Date</label>
                  <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginTop: 6 }}>
                    <select
                      className="entry__input"
                      value={moveDateStr}
                      onChange={(e) => setMoveDateStr(e.target.value)}
                      aria-label="Move log entry to date"
                      style={{ flex: 1 }}
                    >
                      {stripDays.map((d) => (
                        <option key={d} value={d}>
                          {fmtDisplayDate(d)}{d === todayStr ? ' (today)' : ''}
                        </option>
                      ))}
                    </select>
                    <button
                      className="btn btn--primary"
                      onClick={() => handleMove(detail.entries[0].id)}
                      disabled={moveDateStr === entryDate}
                      style={{ fontSize: 12 }}
                    >
                      Move
                    </button>
                  </div>
                </div>
              )}

              {/* Read Mode: Segment History */}
              <div className="drawer__body">
                {detail.entries.length === 0 && (
                  <p style={{ color: 'var(--ink-3)', fontSize: 13 }}>
                    Logged on {fmtDisplayDate(entryDate)} — no journal entry linked.
                  </p>
                )}
                {detail.entries.map((entry, i) => (
                  <div key={entry.id} style={{
                    padding: '12px 0', borderBottom: i < detail.entries.length - 1 ? '1px solid var(--stone-3)' : 'none',
                    display: 'flex', alignItems: 'flex-start', gap: 10,
                  }}>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      {entry.source === 'manual' && !entry.segment_text && (
                        <p style={{ margin: 0, fontSize: 13, color: 'var(--ink-3)' }}>
                          Manual log — {fmtTime(entry.created_at)}
                        </p>
                      )}
                      {entry.source === 'journal' && entry.segment_text && (
                        <blockquote style={{
                          margin: 0, padding: '8px 14px', borderLeft: `3px solid var(--d-color, var(--gold-1))`,
                          fontStyle: 'italic', color: 'var(--ink-2)', fontSize: 13, lineHeight: 1.6,
                        }}>
                          "{entry.segment_text}"
                        </blockquote>
                      )}
                      {entry.source === 'journal' && !entry.segment_text && (
                        <p style={{ margin: 0, fontSize: 13, color: 'var(--ink-3)' }}>
                          Logged on {fmtDisplayDate(entryDate)} — no journal entry linked.
                        </p>
                      )}
                    </div>
                    <button
                      onClick={() => {
                        fetch(`/api/momentum/log/entry/${entry.id}`, { method: 'DELETE' })
                          .then((r) => r.ok ? r.json() : null)
                          .then(() => {
                            setDetail((d) => ({ ...d, entries: d.entries.filter((e) => e.id !== entry.id) }));
                            if (onMoved) onMoved();
                          })
                          .catch(() => {});
                      }}
                      className="btn"
                      aria-label="Delete this log entry"
                      title="Delete this log entry"
                      style={{ flex: '0 0 auto', fontSize: 11, padding: '4px 8px', color: 'var(--ink-3)' }}
                    >
                      Delete
                    </button>
                  </div>
                ))}
              </div>
            </React.Fragment>
          )}
        </div>
      </div>

      {undoToast && (
        <UndoToast
          message={undoToast.message}
          onUndo={handleUndo}
          onDismiss={() => setUndoToast(null)}
        />
      )}
    </React.Fragment>
  );
};

// ── Practice Creation Form ────────────────────────────────────────────────────
const PracticeCreationForm = ({ onClose, onCreated }) => {
  const [name,      setName]      = useState('');
  const [domain,    setDomain]    = useState('');
  const [icon,      setIcon]      = useState('');
  const [iconQuery, setIconQuery] = useState('');
  const [nameError, setNameError] = useState('');
  const [open,      setOpen]      = useState(false);
  const backdropRef = useRef(null);
  const nameRef     = useRef(null);

  useEffect(() => { requestAnimationFrame(() => setOpen(true)); }, []);
  useEffect(() => { if (nameRef.current) nameRef.current.focus(); document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = ''; }; }, []);

  const handleClose = () => { setOpen(false); setTimeout(onClose, 280); };
  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') handleClose(); };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, []);

  const filteredIcons = useMemo(() => {
    const q = iconQuery.trim().toLowerCase();
    if (!q) return AVAILABLE_ICONS;
    return AVAILABLE_ICONS.filter((ic) => ic.includes(q));
  }, [iconQuery]);

  const handleSubmit = () => {
    const trimmed = name.trim();
    if (!trimmed) { setNameError('Name is required.'); return; }
    if (trimmed.length > 60) { setNameError('Max 60 characters.'); return; }
    if (!domain) return;
    if (!icon) return;
    fetch('/api/momentum/registry', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name: trimmed, domain, icon }),
    })
      .then((r) => r.ok ? r.json() : Promise.reject(r))
      .then((practice) => { onCreated(practice); handleClose(); })
      .catch(() => { setNameError('Could not create practice. Try again.'); });
  };

  return (
    <div
      ref={backdropRef}
      className={`drawer-backdrop${open ? ' is-open' : ''}`}
      onMouseDown={(e) => { if (e.target === backdropRef.current) handleClose(); }}
    >
      <div className={`drawer${open ? ' is-open' : ''}`}>
        <button className="drawer__close" onClick={handleClose} aria-label="Close">×</button>
        <header className="drawer__head">
          <span className="label-caps drawer__eye">New Practice</span>
          <h3 className="drawer__title">Create a practice.</h3>
        </header>
        <div className="drawer__body">
          {/* Name */}
          <div className="entry__field">
            <label className="entry__label" htmlFor="pf-name">Name <span style={{ fontWeight: 400, color: 'var(--ink-4)' }}>required · max 60</span></label>
            <input
              id="pf-name"
              ref={nameRef}
              className="entry__input"
              value={name}
              maxLength={60}
              onChange={(e) => { setName(e.target.value); setNameError(''); }}
              placeholder="e.g. Sauna, Meditation, Walk…"
              aria-required="true"
              aria-describedby={nameError ? 'pf-name-error' : undefined}
            />
            {nameError && <span id="pf-name-error" role="alert" style={{ color: 'var(--friction-pink, #e879a0)', fontSize: 12, marginTop: 4, display: 'block' }}>{nameError}</span>}
          </div>

          {/* Domain */}
          <div className="entry__field">
            <label className="entry__label" htmlFor="pf-domain">Domain <span style={{ fontWeight: 400, color: 'var(--ink-4)' }}>required</span></label>
            <select
              id="pf-domain"
              className="entry__input"
              value={domain}
              onChange={(e) => setDomain(e.target.value)}
              aria-required="true"
            >
              <option value="">Select domain…</option>
              {Object.entries(DOMAIN_LABEL).map(([id, label]) => (
                <option key={id} value={id}>{label}</option>
              ))}
            </select>
          </div>

          {/* Icon */}
          <div className="entry__field">
            <label className="entry__label">Icon <span style={{ fontWeight: 400, color: 'var(--ink-4)' }}>required</span></label>
            <input
              className="entry__input"
              placeholder="Search icons…"
              value={iconQuery}
              onChange={(e) => setIconQuery(e.target.value)}
              aria-label="Search icons"
            />
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 8 }}>
              {filteredIcons.map((ic) => (
                <button
                  key={ic}
                  title={ic}
                  aria-label={ic}
                  aria-pressed={icon === ic}
                  onClick={() => setIcon(ic)}
                  style={{
                    width: 40, height: 40, background: icon === ic ? 'var(--gold-soft, rgba(187,152,99,0.14))' : 'var(--stone-2, #2a2a2a)',
                    border: icon === ic ? '2px solid var(--gold-1, #bb9863)' : '1px solid var(--stone-3, #3a3a3a)',
                    borderRadius: 8, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
                  }}
                >
                  <span style={{
                    display: 'block', width: 20, height: 20,
                    maskImage: `url(${_iconHref(ic)})`,
                    WebkitMaskImage: `url(${_iconHref(ic)})`,
                    maskSize: 'contain', maskRepeat: 'no-repeat', maskPosition: 'center',
                    WebkitMaskSize: 'contain', WebkitMaskRepeat: 'no-repeat', WebkitMaskPosition: 'center',
                    backgroundColor: domain ? `var(--d-color, #888)` : '#888',
                  }} className={domain ? (DOMAIN_CLASS[domain] || '') : ''} />
                </button>
              ))}
              {filteredIcons.length === 0 && <span style={{ color: 'var(--ink-4)', fontSize: 12 }}>No icons match.</span>}
            </div>
          </div>
        </div>
        <footer className="drawer__foot">
          <button
            className="btn btn--primary"
            disabled={!name.trim() || !domain || !icon}
            style={(!name.trim() || !domain || !icon) ? { opacity: 0.4, cursor: 'not-allowed' } : null}
            onClick={handleSubmit}
          >
            Create Practice
          </button>
        </footer>
      </div>
    </div>
  );
};

// ── Registry View ─────────────────────────────────────────────────────────────
const RegistryView = ({ registry, onClose, onUpdated }) => {
  const [confirmDelete, setConfirmDelete] = useState(null); // practice object
  const [editingId,     setEditingId]     = useState(null);
  const [editName,      setEditName]      = useState('');
  const [open,          setOpen]          = useState(false);
  const backdropRef = useRef(null);

  useEffect(() => { requestAnimationFrame(() => setOpen(true)); }, []);
  useEffect(() => { document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = ''; }; }, []);

  const handleClose = () => { setOpen(false); setTimeout(onClose, 280); };
  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') handleClose(); };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, []);

  const startEdit = (p) => { setEditingId(p.id); setEditName(p.name); };

  const saveEdit = (practiceId) => {
    const trimmed = editName.trim();
    if (!trimmed) return;
    fetch(`/api/momentum/registry/${practiceId}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name: trimmed }),
    }).then(() => { setEditingId(null); onUpdated(); }).catch(() => {});
  };

  const handleDelete = (practice) => {
    fetch(`/api/momentum/registry/${practice.id}`, { method: 'DELETE' })
      .then((r) => {
        if (!r.ok) return r.json().then((e) => { throw new Error(e.detail); });
        setConfirmDelete(null);
        onUpdated();
      })
      .catch((e) => { alert(e.message || 'Delete failed.'); setConfirmDelete(null); });
  };

  return (
    <React.Fragment>
      <div
        ref={backdropRef}
        className={`drawer-backdrop${open ? ' is-open' : ''}`}
        onMouseDown={(e) => { if (e.target === backdropRef.current) handleClose(); }}
      >
        <div className={`drawer${open ? ' is-open' : ''}`} style={{ width: 'min(520px, 95vw)' }}>
          <button className="drawer__close" onClick={handleClose} aria-label="Close registry">×</button>
          <header className="drawer__head">
            <span className="label-caps drawer__eye">Practice Registry</span>
            <h3 className="drawer__title">Manage practices.</h3>
          </header>
          <div className="drawer__body">
            {registry.length === 0 && (
              <p style={{ color: 'var(--ink-3)', fontSize: 13 }}>No practices yet.</p>
            )}
            {registry.map((p) => {
              const cls = DOMAIN_CLASS[p.domain] || 'dom-admin';
              return (
                <div key={p.id} className={`reg-row ${cls}`} style={{
                  display: 'flex', alignItems: 'center', gap: 12,
                  padding: '10px 0', borderBottom: '1px solid var(--stone-3)',
                }}>
                  <PracticeIcon icon={p.icon} domain={p.domain} name={p.name} size={24} />
                  {editingId === p.id ? (
                    <input
                      className="entry__input"
                      value={editName}
                      maxLength={60}
                      onChange={(e) => setEditName(e.target.value)}
                      onBlur={() => saveEdit(p.id)}
                      onKeyDown={(e) => e.key === 'Enter' && saveEdit(p.id)}
                      autoFocus
                      style={{ flex: 1, fontSize: 13 }}
                      aria-label="Edit practice name"
                    />
                  ) : (
                    <React.Fragment>
                      <span style={{ flex: 1, fontSize: 13, color: 'var(--ink-1)' }}>{p.name}</span>
                      <span style={{ fontSize: 11, color: 'var(--d-color)' }}>
                        {DOMAIN_LABEL[p.domain] || p.domain}
                      </span>
                    </React.Fragment>
                  )}

                  <button
                    className="btn"
                    style={{ fontSize: 11, padding: '3px 8px' }}
                    onClick={() => startEdit(p)}
                    aria-label={`Edit ${p.name}`}
                  >
                    Edit
                  </button>

                  {p.has_linked_action ? (
                    <span
                      title="Delete via the linked Action."
                      aria-label="Linked to Action — delete via Action Detail Drawer"
                      style={{
                        fontSize: 10, padding: '3px 8px', borderRadius: 4,
                        background: 'rgba(120,113,108,0.2)', color: 'var(--ink-3)',
                        border: '1px solid var(--stone-3)', cursor: 'default',
                      }}
                    >
                      ⚭ Action
                    </span>
                  ) : (
                    <button
                      className="btn"
                      style={{ fontSize: 11, padding: '3px 8px', color: 'var(--friction-pink, #e879a0)', borderColor: 'var(--friction-pink, #e879a0)' }}
                      onClick={() => setConfirmDelete(p)}
                      aria-label={`Delete ${p.name}`}
                    >
                      Delete
                    </button>
                  )}
                </div>
              );
            })}
          </div>
        </div>
      </div>

      {/* Delete confirmation dialog — high friction */}
      {confirmDelete && (
        <div
          role="dialog"
          aria-modal="true"
          aria-labelledby="del-dialog-title"
          style={{
            position: 'fixed', inset: 0, zIndex: 500,
            background: 'rgba(0,0,0,0.7)', display: 'flex',
            alignItems: 'center', justifyContent: 'center',
          }}
        >
          <div style={{
            background: 'var(--stone-1, #1c1c1c)', borderRadius: 12,
            padding: '28px 32px', width: 'min(400px, 90vw)',
            border: '1px solid var(--stone-3)',
          }}>
            <h4 id="del-dialog-title" style={{ margin: '0 0 12px', color: 'var(--ink-1)' }}>
              Delete {confirmDelete.name}?
            </h4>
            <p style={{ margin: '0 0 20px', fontSize: 13, color: 'var(--ink-2)', lineHeight: 1.5 }}>
              This will remove all log history. This cannot be undone.
            </p>
            <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
              <button className="btn" onClick={() => setConfirmDelete(null)}>Cancel</button>
              <button
                className="btn"
                style={{ color: 'var(--friction-pink, #e879a0)', borderColor: 'var(--friction-pink, #e879a0)' }}
                onClick={() => handleDelete(confirmDelete)}
              >
                Delete permanently
              </button>
            </div>
          </div>
        </div>
      )}
    </React.Fragment>
  );
};

// ── Momentum log Container ──────────────────────────────────────────────────────────
const MomentumLogContainer = ({
  momentumData, hasPractices, registry,
  suggestion, onSuggestionPromote, onSuggestionIgnore, onSuggestionSnooze,
  onAdd, onCellClick, onOpenRegistry, expanded, onToggleExpand,
}) => {
  const days = expanded ? calendarDays() : momentumDays();
  const todayKey = dateKey(today());

  return (
    <article className="momentum-log">
      <header className="section-head">
        <h2 className="section-title">Momentum log</h2>
        <p className="section-subtitle">rolling practices</p>
        <div className="section-head__actions">
          <button className="btn-primary" onClick={() => onAdd(today())} aria-label="Log practice for today">
            + Log Practice
          </button>
          <button className="btn-secondary" onClick={onOpenRegistry} aria-label="Manage Practices">
            Manage Practices
          </button>
          <button
            className={"btn-secondary" + (expanded ? " is-on" : "")}
            onClick={onToggleExpand}
            aria-pressed={expanded}
            aria-label={expanded ? "Collapse to 7-day view" : "Expand to 4-week calendar"}
          >
            {expanded ? "Collapse −" : "Expand +"}
          </button>
        </div>
      </header>

      {/* Discovery suggestion card — anchored above strip, non-blocking */}
      {suggestion && (
        <SuggestionCard
          suggestion={suggestion}
          onPromote={onSuggestionPromote}
          onIgnore={onSuggestionIgnore}
          onSnooze={onSuggestionSnooze}
        />
      )}

      {/* no_practices: first-run onboarding tile */}
      {!hasPractices ? (
        <div className="momentum-log__strip" role="region" aria-label="Momentum log">
          <FirstRunTile onAdd={onAdd} />
        </div>
      ) : (
        <div className={"momentum-log__strip" + (expanded ? " is-calendar" : "")} role="region" aria-label={expanded ? "Momentum log, 4-week calendar" : "Momentum log, 7-day view"}>
          {days.map((d) => {
            const k       = dateKey(d);
            const entries = momentumData[k] || [];
            return (
              <DayCell
                key={k}
                date={d}
                entries={entries}
                isToday={k === todayKey}
                hasPractices={hasPractices}
                onAdd={onAdd}
                onCellClick={onCellClick}
              />
            );
          })}
        </div>
      )}

      <footer className="momentum-log__legend">
        <span><i className="legend-cell is-today" /> Today</span>
        <span><i className="legend-cell is-uncharted" /> Reflective Gap</span>
      </footer>
    </article>
  );
};

// ── App Root ──────────────────────────────────────────────────────────────────
const PracticeLogApp = () => {
  const [momentumData,   setMomentumData]    = useState({});
  const [hasPractices,  setHasPractices]  = useState(true);
  const [registry,      setRegistry]      = useState([]);
  const [suggestions,   setSuggestions]   = useState([]);
  const [expanded,      setExpanded]      = useState(false);

  // modal state
  const [logForm,       setLogForm]       = useState(null);   // { date }
  const [detailModal,   setDetailModal]   = useState(null);   // { practiceId, entryDate }
  const [createForm,    setCreateForm]    = useState(false);
  const [registryOpen,  setRegistryOpen]  = useState(false);

  const loadMomentumLog = useCallback((isExpanded) => {
    const days = isExpanded ? 30 : 7;
    fetch(`/api/practice/log?days=${days}`)
      .then((r) => r.ok ? r.json() : null)
      .then((d) => {
        if (!d) return;
        setHasPractices(d.has_practices);
        setMomentumData(d.entries || {});
      })
      .catch(() => {});
  }, []);

  const loadRegistry = useCallback(() => {
    fetch('/api/momentum/registry')
      .then((r) => r.ok ? r.json() : null)
      .then((d) => { if (d) setRegistry(d); })
      .catch(() => {});
  }, []);

  const loadSuggestions = useCallback(() => {
    fetch('/api/momentum/suggestions')
      .then((r) => r.ok ? r.json() : null)
      .then((d) => { if (d) setSuggestions(d); })
      .catch(() => {});
  }, []);

  useEffect(() => {
    loadMomentumLog(expanded);
    loadRegistry();
    loadSuggestions();
  }, []);

  const handleToggleExpand = () => {
    const next = !expanded;
    setExpanded(next);
    loadMomentumLog(next);
  };

  const onAdd = (date) => {
    if (!hasPractices) { setCreateForm(true); return; }
    setLogForm({ date });
  };

  const onSaveLog = ({ practice_id, date, source }) => {
    fetch('/api/practice/log', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ practice_id, date, source }),
    })
      .then((r) => r.ok ? r.json() : null)
      .then(() => { loadMomentumLog(expanded); })
      .catch(() => {});
    setLogForm(null);
  };

  const onCellClick = (date, entries) => {
    if (entries.length === 0) return;
    const first = entries[0];
    setDetailModal({ practiceId: first.practice_id, entryDate: dateKey(date) });
  };

  const activeSuggestion = suggestions[0] || null;

  const handlePromote = () => {
    if (!activeSuggestion) return;
    fetch(`/api/momentum/suggestions/${activeSuggestion.id}/promote`, { method: 'POST' })
      .then(() => { loadRegistry(); loadMomentumLog(); loadSuggestions(); })
      .catch(() => {});
  };

  const handleIgnore = () => {
    if (!activeSuggestion) return;
    fetch(`/api/momentum/suggestions/${activeSuggestion.id}/ignore`, { method: 'POST' })
      .then(loadSuggestions).catch(() => {});
  };

  const handleSnooze = () => {
    if (!activeSuggestion) return;
    fetch(`/api/momentum/suggestions/${activeSuggestion.id}/snooze`, { method: 'POST' })
      .then(loadSuggestions).catch(() => {});
  };

  return (
    <div className="plog-app">
      <Sidebar currentView="dashboard" />
      <main className="app__main">
        {/* <Header /> — hidden until user/search bar is ready */}

        {/* Domain Key */}
        <div className="domain-key">
          <span className="label-caps domain-key__eye">Life Domains</span>
          <div className="domain-key__items">
            {Object.entries(DOMAIN_LABEL).map(([id, label]) => (
              <span key={id} className={`domain-key__chip ${DOMAIN_CLASS[id] || ''}`}>
                <span className="domain-key__swatch" style={{ background: 'var(--d-color)' }} />
                {label}
              </span>
            ))}
          </div>
        </div>

        <div className="plog-canvas">
          <MomentumLogContainer
            momentumData={momentumData}
            hasPractices={hasPractices}
            registry={registry}
            suggestion={activeSuggestion}
            onSuggestionPromote={handlePromote}
            onSuggestionIgnore={handleIgnore}
            onSuggestionSnooze={handleSnooze}
            onAdd={onAdd}
            onCellClick={onCellClick}
            onOpenRegistry={() => setRegistryOpen(true)}
            expanded={expanded}
            onToggleExpand={handleToggleExpand}
          />

          <section data-screen-label="02 Action Stream">
            <window.ActionStream />
          </section>

          <section data-screen-label="03 Waypoints">
            <window.Waypoints />
          </section>
        </div>
      </main>

      {/* Log Entry Form (no notes) */}
      {logForm && (
        <LogEntryForm
          initialDate={logForm.date}
          registry={registry}
          onClose={() => setLogForm(null)}
          onSave={onSaveLog}
        />
      )}

      {/* Practice Creation Form */}
      {createForm && (
        <PracticeCreationForm
          onClose={() => setCreateForm(false)}
          onCreated={() => { loadRegistry(); loadMomentumLog(); setCreateForm(false); setHasPractices(true); }}
        />
      )}

      {/* Daily Detail Modal */}
      {detailModal && (
        <DailyDetailModal
          practiceId={detailModal.practiceId}
          entryDate={detailModal.entryDate}
          registry={registry}
          onClose={() => setDetailModal(null)}
          onMoved={loadMomentumLog}
        />
      )}

      {/* Registry View */}
      {registryOpen && (
        <RegistryView
          registry={registry}
          onClose={() => setRegistryOpen(false)}
          onUpdated={() => { loadRegistry(); loadMomentumLog(); }}
        />
      )}
    </div>
  );
};

window.PracticeLogApp = PracticeLogApp;
window.LogEntryForm = LogEntryForm;
window.DailyDetailModal = DailyDetailModal;
