/* ── Check-In tab ──────────────────────────────────────────────────────────
   The weekly-ritual home surface. Between meetings it shows a warm archive of
   past meetings plus a prep prompt; on meeting day (once everyone has checked
   in) it offers "Begin". The full-bleed ritual itself lives in
   screen-checkin-meeting.jsx and is mounted by LedgerApp, not here.

   See CHECK-IN-ARCHITECTURE.md §5.1. State lives in the blob under
   checkInConfig / checkInQuestions / checkIns / checkInMeetings; all cycle and
   phase logic is in data.jsx (checkInStatus, formatCycleKey, cycleExpenses, …).
   ────────────────────────────────────────────────────────────────────────── */

// Seed set for a brand-new household — installed on first tab visit rather
// than in INITIAL_STATE so the copy can evolve without a migration.
const SEED_CHECKIN_QUESTIONS = [
  'How are you coming into this week?',
  'What felt heavy last week?',
  'How can I support you this week?',
  "What's one thing you're looking forward to?",
];

const capWord = (s) => (s ? s.charAt(0).toUpperCase() + s.slice(1) : s);

/* Shared answer renderer for BOTH check-in surfaces (archive card + meeting
   reveal). Preserves typed newlines; renders conservative "- " lists as stacked
   lines; shows a muted "No answer" for empties. Kept here (loaded before the
   meeting file) and window-exported so screen-checkin-meeting.jsx can reuse it. */
function CheckInAnswerText({ raw, className }) {
  const s = String(raw || '').trim();
  if (!s) return <span className={`${className || ''} checkin-answer-empty`}>No answer</span>;
  const lines = parseAnswerListLines(s);
  if (lines) {
    return (
      <div className={`${className || ''} checkin-answer-list`}>
        {lines.map((ln, i) => (
          <div className="checkin-answer-list-item" key={i}>{ln}</div>
        ))}
      </div>
    );
  }
  return <div className={className || ''} style={{ whiteSpace: 'pre-line' }}>{s}</div>;
}

function CheckInScreen({ state, setState, userId, onBeginMeeting, initialConfigOpen }) {
  // Seed questions once for a fresh household (no questions, no meetings).
  const seededRef = React.useRef(false);
  React.useEffect(() => {
    if (seededRef.current) return;
    if ((state.checkInQuestions || []).length === 0 && (state.checkInMeetings || []).length === 0) {
      seededRef.current = true;
      setState(s => {
        if ((s.checkInQuestions || []).length > 0) return s;
        const stamp = Date.now().toString(36);
        return {
          ...s,
          checkInQuestions: SEED_CHECKIN_QUESTIONS.map((prompt, i) => ({
            id: 'cq' + stamp + i, prompt, order: i,
          })),
        };
      });
    }
  }, []);

  const status = checkInStatus(state, userId);
  const [prepOpen, setPrepOpen] = React.useState(false);

  return (
    /* The standard page head, same as every other route. This screen used to
       open straight onto the hero card, which made it the one work surface
       with no title — and on a phone no title at all, since the toolbar hides
       its own copy as "redundant with the page-head h1". The cycle date moved
       up here from the hero's eyebrow rather than being printed twice. */
    <>
      <PageHead sub={`Weekly ritual · ${formatCycleKey(status.cycleKey)}`} title="Check-In" />
      <div className="checkin-screen" data-screen-label="Check-In">
        <CheckInHero
          status={status}
          state={state}
          onAnswer={() => setPrepOpen(true)}
          onBegin={onBeginMeeting}
        />

        {/* preCheckOpen is belt-and-suspenders — the only ways in are the hero
            and roster CTAs, neither of which render while upcoming. It also
            closes the sheet if the window lapses while it's open. */}
        {prepOpen && status.me && status.preCheckOpen && (
          <CheckInPrepSheet
            state={state}
            setState={setState}
            status={status}
            onClose={() => setPrepOpen(false)}
          />
        )}

        <MeetingArchiveList state={state} />
        <CheckInConfigSection state={state} setState={setState} initialOpen={initialConfigOpen} />
      </div>
    </>
  );
}

/* The hero card — a small state machine over checkInStatus().phase. Every
   branch is warm and low-pressure; no red, no counts, no shame. */
function CheckInHero({ status, state, onAnswer, onBegin }) {
  const { phase, me, cycleKey } = status;
  const nQuestions = (state.checkInQuestions || []).length;
  const dayLabel = capWord(status.meetingDay);

  // Who's still missing, and whether a partner has already checked in (so a
  // laggard viewer gets "your turn" rather than the generic prep prompt).
  const others = status.participants.filter(m => !me || m.id !== me.id);
  const anotherIsIn = others.some(m => {
    const ci = checkInForMember(state, cycleKey, m.id);
    return ci && ci.complete;
  });
  const firstMissing = status.missing.find(m => !me || m.id !== me.id) || status.missing[0];

  let body = null;

  if (phase === 'done') {
    body = (
      <div className="checkin-hero-quiet">
        <span className="checkin-hero-check">✓</span>
        <span>You met for {formatCycleKey(cycleKey)}. It's saved below.</span>
      </div>
    );
  } else if (phase === 'in-progress') {
    body = (
      <>
        <div className="checkin-hero-title">You're mid-meeting</div>
        <div className="checkin-hero-sub">Pick up right where you left off.</div>
        <button className="btn primary checkin-hero-cta" onClick={onBegin}>Resume</button>
      </>
    );
  } else if (phase === 'upcoming') {
    // The window hasn't opened yet: say when the check-in is and when it
    // opens, and offer nothing to do. No Answer button, no roster — the
    // questions look back on a week that hasn't finished happening.
    body = (
      <>
        <div className="checkin-hero-title">Next check-in: {formatCycleKey(cycleKey)}</div>
        <div className="checkin-hero-sub">
          Opens {weekdayLong(addDaysIso(cycleKey, -1))} evening — {nQuestions} question{nQuestions === 1 ? '' : 's'}, then the meeting.
        </div>
      </>
    );
  } else if (phase === 'ready') {
    if (status.canBegin) {
      body = (
        <>
          <div className="checkin-hero-title">
            {status.inGrace ? 'Last week is still waiting' : 'Ready for your family meeting'}
          </div>
          <div className="checkin-hero-sub">
            {status.inGrace
              ? `Your ${formatCycleKey(cycleKey)} meeting hasn't happened yet — no rush.`
              : `Both of you have checked in. ${formatCycleKey(cycleKey)}.`}
          </div>
          <button className="btn primary checkin-hero-cta" onClick={onBegin}>Begin</button>
        </>
      );
    } else {
      // Reachable again now that the eve opens pre-check: everyone can be in
      // the night before while the meeting itself still waits for the day.
      body = (
        <div className="checkin-hero-quiet">
          <span className="checkin-hero-check">✓</span>
          <span>{status.isEve ? 'Both checked in — see you tomorrow.' : `Both checked in — see you ${dayLabel}.`}</span>
        </div>
      );
    }
  } else if (phase === 'waiting') {
    body = (
      <>
        <div className="checkin-hero-title">You've checked in ✓</div>
        <div className="checkin-hero-sub">
          {firstMissing ? `${firstMissing.name} hasn't yet — the meeting opens once they do.` : 'Waiting on the rest of the household.'}
        </div>
      </>
    );
  } else { // 'prep'
    body = (
      <>
        <div className="checkin-hero-title">
          {anotherIsIn && firstMissing == null ? 'Your turn' : 'Answer your check-in for this week'}
        </div>
        <div className="checkin-hero-sub">
          {anotherIsIn
            ? `${others.find(m => { const ci = checkInForMember(state, cycleKey, m.id); return ci && ci.complete; })?.name || 'Your partner'} has checked in — your turn.`
            : `${nQuestions} question${nQuestions === 1 ? '' : 's'}, about two minutes. Sealed until ${status.isEve ? "tomorrow's meeting" : 'the meeting'}.`}
        </div>
        {me && <button className="btn primary checkin-hero-cta" onClick={onAnswer}>Answer</button>}
      </>
    );
  }

  return (
    <div className={`checkin-hero checkin-hero-${phase}`}>
      {body}
      {/* Pre-check roster — a per-person status box, only once the window is
          open and while we're still gathering check-ins. Redundant once
          mid-meeting/done, and premature while the cycle is upcoming. */}
      {status.preCheckOpen && phase !== 'in-progress' && phase !== 'done' && (
        <CheckInRoster status={status} state={state} onAnswer={onAnswer} />
      )}
    </div>
  );
}

/* One rounded box per participant showing whether they've filed their pre-check
   for the active cycle. A neutral initials circle flips to a green check and the
   box tints green the moment they're in — derived live from checkIns, so a
   partner checking in on their own device turns their box green here via the
   realtime sync. Your own not-yet box doubles as a shortcut into the prep sheet. */
function CheckInRoster({ status, state, onAnswer }) {
  const { participants, cycleKey, me } = status;
  if (!participants || participants.length === 0) return null;
  const names = checkInDisplayNames(participants);
  return (
    <div className="checkin-roster">
      {participants.map(m => {
        const ci = checkInForMember(state, cycleKey, m.id);
        const isIn = !!(ci && ci.complete);
        const isMe = !!(me && m.id === me.id);
        const clickable = isMe && !isIn;
        // Only your own not-yet box is a real button; everyone else's is a
        // passive readout, and a focusable control that does nothing is worse
        // than a div. `button.checkin-roster-box` in app.css carries the reset.
        const Box = clickable ? 'button' : 'div';
        return (
          <Box key={m.id}
               className={`checkin-roster-box${isIn ? ' is-in' : ''}${clickable ? ' is-actionable' : ''}`}
               {...(clickable ? {
                 type: 'button',
                 onClick: onAnswer,
                 'aria-label': 'Answer your check-in',
               } : {})}>
            <span className="checkin-roster-circle">
              {isIn ? (
                <svg className="checkin-roster-check" viewBox="0 0 24 24" width="14" height="14"
                     fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
                  <polyline points="20 6 9 17 4 12" />
                </svg>
              ) : (
                <span className="checkin-roster-initials">{nameInitials(m.name)}</span>
              )}
            </span>
            <span className="checkin-roster-meta">
              <span className="checkin-roster-name">
                {names[m.id] || m.name}
                {isMe && <span className="checkin-roster-you">You</span>}
              </span>
              <span className="checkin-roster-status">{isIn ? 'Checked in' : 'Not yet'}</span>
            </span>
          </Box>
        );
      })}
    </div>
  );
}

/* The prep sheet — a calm, considered answering surface. Auto-saves on blur;
   no Submit button. Answers are sealed (never shown to the partner) until the
   meeting begins. */
function CheckInPrepSheet({ state, setState, status, onClose }) {
  const questions = [...(state.checkInQuestions || [])].sort((a, b) => a.order - b.order);
  const cycleKey = status.cycleKey;
  const memberId = status.me && status.me.id;

  const [answers, setAnswers] = React.useState(() => {
    const mine = checkInForMember(state, cycleKey, memberId);
    return mine && mine.answers ? { ...mine.answers } : {};
  });

  const allAnswered = questions.length > 0 && questions.every(q => (answers[q.id] || '').trim().length > 0);

  const save = (next) => {
    if (!memberId) return;
    setState(s => {
      const qs = s.checkInQuestions || [];
      const complete = qs.length > 0 && qs.every(q => (next[q.id] || '').trim().length > 0);
      const existing = (s.checkIns || []).find(c => c.cycleKey === cycleKey && c.memberId === memberId);
      const entry = {
        id: (existing && existing.id) || ('ci' + Date.now().toString(36)),
        cycleKey, memberId,
        answers: next,
        complete,
        updatedAt: new Date().toISOString(),
      };
      const rest = (s.checkIns || []).filter(c => !(c.cycleKey === cycleKey && c.memberId === memberId));
      return { ...s, checkIns: [...rest, entry] };
    });
  };

  const setAnswer = (qid, val) => setAnswers(a => ({ ...a, [qid]: val }));

  return (
    <Sheet onClose={onClose} modalClassName="modal checkin-prep-modal">
      {(requestClose) => (
        <>
          <div className="modal-header">
            <div className="modal-sub">for {formatCycleKey(cycleKey)}</div>
            <div className="modal-title">Your check-in</div>
          </div>
          <div className="modal-body checkin-prep-body">
            <p className="checkin-prep-intro muted">
              Take your time. Only you can see these until the meeting — then you'll open them together.
            </p>
            {questions.length === 0 && (
              <p className="muted">No questions yet. Add some in the Check-In settings below.</p>
            )}
            {questions.map((q, i) => (
              <div className="checkin-prep-q" key={q.id}>
                <label className="checkin-prep-prompt">
                  <span className="checkin-prep-num">{i + 1}</span>
                  {q.prompt}
                </label>
                <textarea
                  className="input checkin-prep-answer"
                  rows={3}
                  value={answers[q.id] || ''}
                  placeholder="Write as much or as little as you like…"
                  onChange={e => setAnswer(q.id, e.target.value)}
                  onBlur={() => save(answers)}
                />
              </div>
            ))}
          </div>
          <div className="modal-footer checkin-prep-footer">
            <span className={`checkin-prep-status ${allAnswered ? 'done' : ''}`}>
              {allAnswered ? "You've checked in for this week ✓" : 'Saved as you go'}
            </span>
            <button className="btn primary" onClick={() => { save(answers); requestClose(); }}>
              {allAnswered ? 'Done' : 'Close'}
            </button>
          </div>
        </>
      )}
    </Sheet>
  );
}

// Build a display list of meetings interleaved with "No meeting" gap markers
// for skipped weeks strictly between two held meetings (bounded).
function buildArchiveItems(meetings) {
  const items = [];
  for (let i = 0; i < meetings.length; i++) {
    items.push({ type: 'meeting', key: meetings[i].id, meeting: meetings[i] });
    const next = meetings[i + 1];
    if (next) {
      let k = addDaysIso(meetings[i].cycleKey, -7);
      let guard = 0;
      while (k > next.cycleKey && guard < 12) {
        items.push({ type: 'gap', key: 'gap-' + k, cycleKey: k });
        k = addDaysIso(k, -7);
        guard++;
      }
    }
  }
  return items;
}

/* The tab surfaces only the most recent check-in; everything older lives behind
   an archive button that opens a lightbox of the full history. */
function MeetingArchiveList({ state }) {
  const [archiveOpen, setArchiveOpen] = React.useState(false);
  const meetings = (state.checkInMeetings || [])
    .filter(m => m.completedAt)
    .sort((a, b) => (a.cycleKey < b.cycleKey ? 1 : -1));

  if (meetings.length === 0) {
    return (
      <div className="checkin-archive">
        <div className="checkin-archive-head">Your meetings</div>
        <div className="checkin-archive-empty">
          Your first meeting will show up here. It'll be worth keeping.
        </div>
      </div>
    );
  }

  const latest = meetings[0];
  const olderCount = meetings.length - 1;

  return (
    <div className="checkin-archive">
      <div className="checkin-archive-head">Last check-in</div>
      <MeetingArchiveCard state={state} meeting={latest} defaultExpanded />

      {olderCount > 0 && (
        <button className="checkin-archive-btn" onClick={() => setArchiveOpen(true)}>
          <span>Past check-ins</span>
          <span className="checkin-archive-btn-count">{olderCount}</span>
          <span className="checkin-archive-btn-chev" aria-hidden="true">›</span>
        </button>
      )}

      {archiveOpen && <CheckInArchiveModal state={state} meetings={meetings} onClose={() => setArchiveOpen(false)} />}
    </div>
  );
}

/* Lightbox with the full history — every held meeting plus gap markers. */
function CheckInArchiveModal({ state, meetings, onClose }) {
  const items = buildArchiveItems(meetings);
  return (
    <Sheet onClose={onClose} modalClassName="modal checkin-archive-modal">
      {(requestClose) => (
        <>
          <div className="modal-header">
            <div className="modal-sub">A small record of your weeks together</div>
            <div className="modal-title">Past check-ins</div>
          </div>
          <div className="modal-body checkin-archive-modal-body">
            {items.map(it => (
              it.type === 'gap'
                ? <div className="checkin-gap" key={it.key}>No meeting the week of {formatCycleKey(it.cycleKey)}</div>
                : <MeetingArchiveCard key={it.key} state={state} meeting={it.meeting} defaultExpanded={false} />
            ))}
          </div>
          <div className="modal-footer checkin-config-footer">
            <button className="btn primary" onClick={requestClose}>Done</button>
          </div>
        </>
      )}
    </Sheet>
  );
}

/* Group commonplace actions by note → one row per note, net toggles within each
   group, and produce a plain-words summary. Legacy actions (no snapshot fields)
   whose note is gone AND have no title collapse into a single trailing line.
   Returns { rows: [{ noteId, title, summary }], removedCount }.

   Netting: key each action by target — noteId alone (note-level) or noteId+itemId
   (item-level) — keep only the LAST action per target. A target whose final
   action UNDOES an earlier do-action within the same meeting is dropped entirely
   (item-done then item-undone ⇒ omit). A target whose ONLY action is an undo
   (e.g. a pre-completed note reopened here) is genuine and surfaces as
   "Reopened". */
function buildRevisitedGroups(actions, liveNoteTitle) {
  const DO_OF = { 'item-undone': 'item-done', 'note-undone': 'note-done',
                  'note-unflagged': 'note-flagged', 'note-unarchived': 'note-archived' };
  const byNote = {};
  actions.forEach(a => {
    const nid = a.noteId;
    if (!byNote[nid]) byNote[nid] = { noteId: nid, snapshotTitle: a.noteTitle || null, targets: {} };
    if (!byNote[nid].snapshotTitle && a.noteTitle) byNote[nid].snapshotTitle = a.noteTitle;
    const targetKey = a.itemId ? `i:${a.itemId}` : 'note';
    if (!byNote[nid].targets[targetKey]) byNote[nid].targets[targetKey] = [];
    byNote[nid].targets[targetKey].push(a); // ordered history per target
  });

  const rows = [];
  let removedCount = 0;

  Object.values(byNote).forEach(group => {
    const finals = [];
    Object.values(group.targets).forEach(history => {
      const last = history[history.length - 1];
      const doAction = DO_OF[last.action];
      // Final action is an undo AND an earlier matching do-action exists → cancels out.
      if (doAction && history.slice(0, -1).some(a => a.action === doAction)) return;
      finals.push(last);
    });
    if (finals.length === 0) return; // everything toggled back — omit the note

    const live = liveNoteTitle(group.noteId);
    const title = group.snapshotTitle || live || '[a note since removed]';
    const isRemoved = !group.snapshotTitle && !live;

    if (isRemoved) { removedCount += finals.length; return; }

    // Humanized, aggregated summary.
    const itemsChecked = finals.filter(a => a.itemId && a.action === 'item-done').length;
    const itemsReopened = finals.filter(a => a.itemId && a.action === 'item-undone').length;
    const facts = [];
    if (itemsChecked > 0) facts.push(`${itemsChecked} checked off`);
    if (itemsReopened > 0) facts.push(`${itemsReopened} reopened`);
    finals.filter(a => !a.itemId).forEach(a => {
      if (a.action === 'note-done') facts.push('Completed');
      else if (a.action === 'note-undone') facts.push('Reopened');
      else if (a.action === 'note-archived') facts.push('Sent to archive');
      else if (a.action === 'note-unarchived') facts.push('Restored');
      else if (a.action === 'note-flagged') facts.push('Flagged');
      else if (a.action === 'note-unflagged') facts.push('Flag cleared');
    });
    const summary = facts.length ? facts.join(' · ') : 'Revisited';
    rows.push({ noteId: group.noteId, title, summary });
  });

  return { rows, removedCount };
}

function MeetingArchiveCard({ state, meeting, defaultExpanded }) {
  const [open, setOpen] = React.useState(!!defaultExpanded);
  // Archive "THAT WEEK" recomputes live and is NOT closed-month-filtered — every
  // archived meeting's month eventually closes, and filtering here would
  // retroactively zero out past records. (The live meeting screen filters; this
  // does not — intentional asymmetry.)
  const spend = cycleExpenses(state.expenses, meeting.cycleKey);
  const spendTotal = spend.reduce((s, e) => s + expenseNet(e), 0);

  // First-name-only labels for the Q&A section, disambiguated on collision (4a).
  const displayNames = checkInDisplayNames(
    (meeting.responses || []).map(r => ({ id: r.memberId, name: r.name })));

  // Resolution order for display text: snapshot → live lookup → removed.
  const goalName = (a) => {
    if (a.goalName) return a.goalName;
    const g = (state.goals || []).find(x => x.id === a.goalId);
    return g ? g.name : '[a goal since removed]';
  };
  const liveNoteTitle = (noteId) => {
    const n = ((state.brainDump && state.brainDump.notes) || []).find(x => x.id === noteId);
    return n ? (n.title || n.body || 'Untitled note') : null;
  };

  const revisited = buildRevisitedGroups(meeting.commonplaceActions || [], liveNoteTitle);

  return (
    <div className={`checkin-archive-card ${open ? 'open' : ''}`}>
      <button className="checkin-archive-card-head" onClick={() => setOpen(o => !o)}>
        <div className="checkin-archive-card-head-main">
          {/* The date is the title. It's what you'd actually search an archive
              by, and it's the one thing every meeting has — headlines were
              optional, so the list used to be a mix of two kinds of card. */}
          <div className="checkin-archive-headline">{formatCycleKey(meeting.cycleKey)}</div>
        </div>
        <span className="checkin-archive-caret">{open ? '▾' : '▸'}</span>
      </button>

      {open && (
        <div className="checkin-archive-card-body">
          {/* Check-in Q&A (from the meeting snapshot) */}
          {(meeting.questions || []).length > 0 && (
            <div className="checkin-archive-section">
              <div className="checkin-archive-section-label">Check-in</div>
              {meeting.questions.map(q => (
                <div className="checkin-archive-qa" key={q.id}>
                  <div className="checkin-archive-q">{q.prompt}</div>
                  {(meeting.responses || []).map(r => (
                    <div className="checkin-archive-a" key={r.memberId}>
                      <span className="checkin-archive-a-who">{displayNames[r.memberId] || r.name}</span>
                      <CheckInAnswerText raw={r.answers && r.answers[q.id]} className="checkin-archive-a-text" />
                    </div>
                  ))}
                </div>
              ))}
            </div>
          )}

          {/* Week's spend (recomputed live from current expenses) */}
          <div className="checkin-archive-section">
            <div className="checkin-archive-section-label">That week</div>
            <div className="checkin-archive-spend">
              <span className="num">{fmt(spendTotal, { cents: false })}</span>
              <span className="muted"> across {spend.length} expense{spend.length === 1 ? '' : 's'}</span>
            </div>
          </div>

          {/* Allocations (snapshot → live lookup → removed) */}
          {(meeting.allocations || []).length > 0 && (
            <div className="checkin-archive-section">
              <div className="checkin-archive-section-label">Allocated</div>
              {meeting.allocations.map(a => (
                <div className="checkin-archive-line" key={a.id}>
                  <span>{goalName(a)}</span>
                  <span className="num pos">{fmt(a.amount)}</span>
                </div>
              ))}
            </div>
          )}

          {/* Commonplace threads revisited — grouped per note, netted, humanized */}
          {(revisited.rows.length > 0 || revisited.removedCount > 0) && (
            <div className="checkin-archive-section">
              <div className="checkin-archive-section-label">Revisited</div>
              {revisited.rows.map(row => (
                <div className="checkin-archive-line" key={row.noteId}>
                  <span>{row.title}</span>
                  <span className="muted checkin-archive-action">{row.summary}</span>
                </div>
              ))}
              {revisited.removedCount > 0 && (
                <div className="checkin-archive-removed muted">
                  {revisited.removedCount} update{revisited.removedCount === 1 ? '' : 's'} to notes since removed
                </div>
              )}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

/* Config lives inside the Check-In tab (not app Settings) — the config IS the
   feature's shape. The trigger opens a dimmed lightbox with the full editor. */
function CheckInConfigSection({ state, setState, initialOpen }) {
  // initialOpen arrives from the Get-started checklist, which sends the
  // user here specifically to shape the ritual — open the sheet for them.
  const [open, setOpen] = React.useState(!!initialOpen);
  return (
    <div className="checkin-config">
      <button className="checkin-config-open" onClick={() => setOpen(true)}>
        <span className="checkin-config-open-glyph" aria-hidden="true">✦</span>
        <span className="checkin-config-open-text">
          <span className="checkin-config-open-title">Check-In settings</span>
          <span className="checkin-config-open-sub">Questions, meeting day, and savings cadence</span>
        </span>
        <span className="checkin-config-open-chev" aria-hidden="true">›</span>
      </button>
      {open && <CheckInConfigModal state={state} setState={setState} onClose={() => setOpen(false)} />}
    </div>
  );
}

function CheckInConfigModal({ state, setState, onClose }) {
  const cfg = getCheckInConfig(state);
  const questions = [...(state.checkInQuestions || [])].sort((a, b) => a.order - b.order);

  // Stamp the first deliberate edit. checkInConfig ships pre-filled
  // (Sunday / monthly), so the Get-started checklist has no way to tell
  // "chose Sunday" from "never opened this" without a marker. Written
  // once — later edits leave the original date alone. Note the
  // auto-seeding of starter questions in CheckInScreen deliberately
  // doesn't come through here: it isn't the household's choice.
  const stampedCfg = (s) => ({
    ...getCheckInConfig(s),
    configuredAt: (s.checkInConfig && s.checkInConfig.configuredAt) || isoDate(TODAY),
  });

  const writeQuestions = (next) =>
    setState(s => ({
      ...s,
      checkInQuestions: next.map((q, i) => ({ ...q, order: i })),
      checkInConfig: stampedCfg(s),
    }));
  const editQuestion = (id, prompt) => writeQuestions(questions.map(q => q.id === id ? { ...q, prompt } : q));
  const deleteQuestion = (id) => writeQuestions(questions.filter(q => q.id !== id));
  const addQuestion = () => writeQuestions([...questions, { id: 'cq' + Date.now().toString(36), prompt: '' }]);
  const move = (id, dir) => {
    const i = questions.findIndex(q => q.id === id);
    const j = i + dir;
    if (i < 0 || j < 0 || j >= questions.length) return;
    const copy = [...questions];
    [copy[i], copy[j]] = [copy[j], copy[i]];
    writeQuestions(copy);
  };
  const setCfg = (patch) => setState(s => ({ ...s, checkInConfig: { ...stampedCfg(s), ...patch } }));

  return (
    <Sheet onClose={onClose} modalClassName="modal checkin-config-modal">
      {(requestClose) => (
        <>
          <div className="modal-header">
            <div className="modal-sub">The shape of your weekly ritual</div>
            <div className="modal-title">Check-In settings</div>
          </div>

          <div className="modal-body checkin-config-body">
            {/* Question authoring */}
            <div className="checkin-config-block">
              <div className="checkin-config-label">Your questions</div>
              <p className="muted checkin-config-hint">
                Everyone answers these each week. Keep them warm — you'll read the answers out loud together.
              </p>
              <div className="checkin-config-qlist">
                {questions.map((q, i) => (
                  <div className="checkin-config-q" key={q.id}>
                    <span className="checkin-config-q-num">{i + 1}</span>
                    <input
                      className="input checkin-config-q-input"
                      value={q.prompt}
                      placeholder="Ask something…"
                      onChange={e => editQuestion(q.id, e.target.value)}
                    />
                    <div className="checkin-config-q-actions">
                      <button className="checkin-q-btn" title="Move up" disabled={i === 0} onClick={() => move(q.id, -1)} aria-label="Move up">
                        <svg viewBox="0 0 16 16" width="13" height="13" aria-hidden="true"><path d="M8 3.5 L8 12.5 M4 7.5 L8 3.5 L12 7.5" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/></svg>
                      </button>
                      <button className="checkin-q-btn" title="Move down" disabled={i === questions.length - 1} onClick={() => move(q.id, 1)} aria-label="Move down">
                        <svg viewBox="0 0 16 16" width="13" height="13" aria-hidden="true"><path d="M8 3.5 L8 12.5 M4 8.5 L8 12.5 L12 8.5" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/></svg>
                      </button>
                      <button className="checkin-q-btn danger" title="Remove" onClick={() => deleteQuestion(q.id)} aria-label="Remove">
                        <svg viewBox="0 0 16 16" width="13" height="13" aria-hidden="true"><path d="M4 4 L12 12 M12 4 L4 12" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/></svg>
                      </button>
                    </div>
                  </div>
                ))}
              </div>
              <button className="checkin-config-add" onClick={addQuestion}>
                <span className="checkin-config-add-plus">+</span> Add a question
              </button>
            </div>

            {/* Meeting day */}
            <div className="checkin-config-block">
              <div className="checkin-config-label">Meeting day</div>
              <div className="checkin-day-pills">
                {MEETING_DAYS.map(d => (
                  <button
                    key={d}
                    className={`checkin-day-pill ${cfg.meetingDay === d ? 'active' : ''}`}
                    onClick={() => setCfg({ meetingDay: d })}>
                    {capWord(d).slice(0, 3)}
                  </button>
                ))}
              </div>
            </div>

            {/* Savings cadence */}
            <div className="checkin-config-block">
              <div className="checkin-config-label">Savings allocation</div>
              <div className="checkin-cadence-toggle">
                <button
                  className={`checkin-cadence-opt ${cfg.savingsCadence === 'weekly' ? 'active' : ''}`}
                  onClick={() => setCfg({ savingsCadence: 'weekly' })}>Weekly</button>
                <button
                  className={`checkin-cadence-opt ${cfg.savingsCadence === 'monthly' ? 'active' : ''}`}
                  onClick={() => setCfg({ savingsCadence: 'monthly' })}>Monthly</button>
              </div>
              <p className="muted checkin-config-hint">
                Weekly keeps decisions small. Monthly lets the pool build up before you allocate.
              </p>
            </div>
          </div>

          <div className="modal-footer checkin-config-footer">
            <button className="btn primary" onClick={requestClose}>Done</button>
          </div>
        </>
      )}
    </Sheet>
  );
}

Object.assign(window, { CheckInScreen, CheckInAnswerText, buildRevisitedGroups });
