/* === Modals: Add expense, Add fixed cost, Add goal === */

function ExpenseModal({ state, setState, onClose, setActiveMonth, editing }) {
  const { tags, family } = state;
  const [requestClose, sheetClass] = useSheet(onClose);
  const todayIso = `${TODAY.getFullYear()}-${String(TODAY.getMonth()+1).padStart(2,'0')}-${String(TODAY.getDate()).padStart(2,'0')}`;
  const [merchant, setMerchant] = React.useState(editing?.merchant || '');
  const [amount, setAmount]     = React.useState(editing ? String(editing.amount) : '');
  const [tagId, setTagId]       = React.useState(editing?.tagId || '');
  const [memberId, setMemberId] = React.useState(editing?.memberId || '');
  const [date, setDate]         = React.useState(editing?.date || todayIso);
  const [note, setNote]         = React.useState(editing?.note || '');

  const amountNum = parseFloat(amount);
  const canSubmit = merchant.trim() && amount && !isNaN(amountNum) && amountNum > 0;

  const submit = () => {
    if (!canSubmit) return;
    if (editing) {
      const updated = { ...editing, merchant: merchant.trim(), amount: amountNum, tagId: tagId || null, memberId: memberId || null, date, note };
      setState(s => ({ ...s, expenses: s.expenses.map(x => x.id === editing.id ? updated : x) }));
    } else {
      const e = {
        id: 'e' + Date.now().toString(36),
        merchant: merchant.trim(), amount: amountNum, tagId: tagId || null, memberId: memberId || null, date, note,
      };
      setState(s => ({ ...s, expenses: [e, ...s.expenses] }));
    }
    if (setActiveMonth) setActiveMonth(monthKey(date));
    requestClose();
  };

  const onKeyDown = (ev) => { if (ev.key === 'Enter') submit(); };

  return (
    <div className={`modal-backdrop ${sheetClass}`} onClick={requestClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-header">
          <div className="modal-sub">{editing ? 'Edit transaction' : 'New transaction'}</div>
          <div className="modal-title">{editing ? 'Edit expense' : 'Add an expense'}</div>
        </div>
        <div className="modal-body">
          <div className="field">
            <span className="field-label">Amount</span>
            <div className="amount-wrap">
              <AmountInput value={amount} onChange={setAmount} autoFocus />
            </div>
          </div>
          <div className="field">
            <span className="field-label">Merchant</span>
            <input className="input" value={merchant} onChange={e => setMerchant(e.target.value)} onKeyDown={onKeyDown} placeholder="e.g. Trader Joe's" />
          </div>
          <div className="row gap-sm">
            <div className="field" style={{ flex: 1 }}>
              <span className="field-label">Date</span>
              <input className="input" type="date" value={date} onChange={e => setDate(e.target.value)} onKeyDown={onKeyDown} />
            </div>
            <div className="field" style={{ flex: 1 }}>
              <span className="field-label">Note (optional)</span>
              <input className="input" value={note} onChange={e => setNote(e.target.value)} onKeyDown={onKeyDown} placeholder="" />
            </div>
          </div>
          <div className="field">
            <span className="field-label">Tag · optional</span>
            <div className="tag-picker">
              <Tag tag={{ name: 'Untagged', color: 'slate' }} selected={tagId === ''} onClick={() => setTagId('')} />
              {tags.map(t => (
                <Tag key={t.id} tag={t} selected={tagId === t.id} onClick={() => setTagId(t.id)} />
              ))}
            </div>
          </div>
          <div className="field">
            <span className="field-label">Charged to · optional</span>
            <div className="member-picker">
              <button type="button"
                className={`member-pick ${memberId === '' ? 'selected' : ''}`}
                onClick={() => setMemberId('')}>
                <span className="member-chip" style={{ background: 'var(--ink-3)' }}>—</span>
                <span>Unassigned</span>
              </button>
              {family.map(m => (
                <button key={m.id} type="button"
                  className={`member-pick ${memberId === m.id ? 'selected' : ''}`}
                  onClick={() => setMemberId(m.id)}>
                  <span className="member-chip" style={{ background: TAG_COLORS[m.color] }}>{nameInitials(m.name)}</span>
                  <span>{m.name}</span>
                </button>
              ))}
            </div>
          </div>
        </div>
        <div className="modal-footer">
          <button className="btn ghost" onClick={requestClose}>Cancel</button>
          <button className="btn primary" onClick={submit} disabled={!canSubmit}>{editing ? 'Save changes' : 'Add expense'}</button>
        </div>
      </div>
    </div>
  );
}

function FixedModal({ state, setState, onClose }) {
  const { tags, family } = state;
  const [requestClose, sheetClass] = useSheet(onClose);
  const [name, setName] = React.useState('');
  const [amount, setAmount] = React.useState('');
  const [frequency, setFrequency] = React.useState('monthly');
  const [due, setDue] = React.useState(1);
  const [dueMonth, setDueMonth] = React.useState(1);
  const [tagId, setTagId] = React.useState(tags[0]?.id);
  const [memberId, setMemberId] = React.useState('');

  const amountNum = parseFloat(amount);
  const canSubmit = name.trim() && amount && !isNaN(amountNum) && amountNum > 0;

  // Reset `due` to a sensible default when frequency changes.
  const pickFrequency = (next) => {
    setFrequency(next);
    if (next === 'weekly') setDue(1);
    else setDue(1);
  };

  const submit = () => {
    if (!canSubmit) return;
    const f = {
      id: 'f' + Date.now().toString(36),
      name: name.trim(), amount: amountNum, frequency, due: parseInt(due) || 1, tagId, memberId: memberId || null,
    };
    if (frequency === 'yearly') f.dueMonth = parseInt(dueMonth) || 1;
    setState(s => ({ ...s, fixed: [...s.fixed, f] }));
    requestClose();
  };

  const onKeyDown = (ev) => { if (ev.key === 'Enter') submit(); };

  return (
    <div className={`modal-backdrop ${sheetClass}`} onClick={requestClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-header">
          <div className="modal-sub">Recurring expense</div>
          <div className="modal-title">Add a fixed cost</div>
        </div>
        <div className="modal-body">
          <div className="field">
            <span className="field-label">Amount</span>
            <div className="amount-wrap">
              <AmountInput value={amount} onChange={setAmount} autoFocus />
            </div>
          </div>
          <div className="field">
            <span className="field-label">Name</span>
            <input className="input" value={name} onChange={e => setName(e.target.value)} onKeyDown={onKeyDown} placeholder="e.g. Rent" />
          </div>
          <div className="field">
            <span className="field-label">Repeats</span>
            <div className="member-picker">
              {[
                { id: 'weekly', label: 'Weekly' },
                { id: 'monthly', label: 'Monthly' },
                { id: 'yearly', label: 'Yearly' },
              ].map(opt => (
                <button key={opt.id} type="button"
                  className={`member-pick ${frequency === opt.id ? 'selected' : ''}`}
                  onClick={() => pickFrequency(opt.id)}>
                  <span>{opt.label}</span>
                </button>
              ))}
            </div>
          </div>
          {frequency === 'weekly' && (
            <div className="field">
              <span className="field-label">Day of week</span>
              <select className="select" value={due} onChange={e => setDue(e.target.value)} onKeyDown={onKeyDown}>
                <option value="0">Sunday</option>
                <option value="1">Monday</option>
                <option value="2">Tuesday</option>
                <option value="3">Wednesday</option>
                <option value="4">Thursday</option>
                <option value="5">Friday</option>
                <option value="6">Saturday</option>
              </select>
            </div>
          )}
          {frequency === 'monthly' && (
            <div className="field">
              <span className="field-label">Due day of month</span>
              <input className="input" type="number" min="1" max="31" value={due} onChange={e => setDue(e.target.value)} onKeyDown={onKeyDown} />
            </div>
          )}
          {frequency === 'yearly' && (
            <div className="row gap-sm">
              <div className="field" style={{ flex: 1 }}>
                <span className="field-label">Month</span>
                <select className="select" value={dueMonth} onChange={e => setDueMonth(e.target.value)} onKeyDown={onKeyDown}>
                  {MONTH_NAMES.map((mn, i) => <option key={i} value={i + 1}>{mn}</option>)}
                </select>
              </div>
              <div className="field" style={{ flex: 1 }}>
                <span className="field-label">Day</span>
                <input className="input" type="number" min="1" max="31" value={due} onChange={e => setDue(e.target.value)} onKeyDown={onKeyDown} />
              </div>
            </div>
          )}
          <div className="field">
            <span className="field-label">Tag</span>
            <div className="tag-picker">
              {tags.map(t => (
                <Tag key={t.id} tag={t} selected={tagId === t.id} onClick={() => setTagId(t.id)} />
              ))}
            </div>
          </div>
          <div className="field">
            <span className="field-label">Charged to · optional</span>
            <div className="member-picker">
              <button type="button"
                className={`member-pick ${!memberId ? 'selected' : ''}`}
                onClick={() => setMemberId('')}>
                <span className="member-chip" style={{ background: 'var(--paper-3)', color: 'var(--ink-3)' }}>—</span>
                <span>Unassigned</span>
              </button>
              {family.map(m => (
                <button key={m.id} type="button"
                  className={`member-pick ${memberId === m.id ? 'selected' : ''}`}
                  onClick={() => setMemberId(m.id)}>
                  <span className="member-chip" style={{ background: TAG_COLORS[m.color] }}>{nameInitials(m.name)}</span>
                  <span>{m.name}</span>
                </button>
              ))}
            </div>
          </div>
        </div>
        <div className="modal-footer">
          <button className="btn ghost" onClick={requestClose}>Cancel</button>
          <button className="btn primary" onClick={submit} disabled={!canSubmit}>Add fixed cost</button>
        </div>
      </div>
    </div>
  );
}

function GoalModal({ state, setState, onClose, editing }) {
  const [requestClose, sheetClass] = useSheet(onClose);
  const [name, setName] = React.useState(editing?.name || '');
  const [target, setTarget] = React.useState(editing ? String(editing.target) : '');
  const [monthly, setMonthly] = React.useState(editing ? String(editing.monthly) : '');
  const [deadline, setDeadline] = React.useState(editing?.deadline || '2026-12-01');
  const [color, setColor] = React.useState(editing?.color || 'terracotta');
  const [photo, setPhoto] = React.useState(editing?.photo || '');
  const [photoError, setPhotoError] = React.useState('');

  const targetNum = parseFloat(target);
  const canSubmit = name.trim() && target && !isNaN(targetNum) && targetNum > 0;

  const onPickPhoto = async (file) => {
    setPhotoError('');
    if (!file) return;
    try {
      const dataUrl = await resizeImageFile(file);
      setPhoto(dataUrl);
    } catch (err) {
      setPhotoError('Could not load that image.');
    }
  };

  const submit = () => {
    if (!canSubmit) return;
    if (editing) {
      const updated = {
        ...editing,
        name: name.trim(), target: targetNum, deadline,
        monthly: parseFloat(monthly) || 100, color, photo: photo || null,
      };
      setState(s => ({ ...s, goals: s.goals.map(x => x.id === editing.id ? updated : x) }));
    } else {
      const g = {
        id: 'g' + Date.now().toString(36),
        name: name.trim(), target: targetNum, saved: 0,
        deadline, monthly: parseFloat(monthly) || 100, color,
        photo: photo || null,
      };
      setState(s => ({ ...s, goals: [...s.goals, g] }));
    }
    requestClose();
  };

  const remove = () => {
    if (!editing) return;
    const refund = editing.saved || 0;
    const msg = refund > 0
      ? `Delete "${editing.name}"? ${fmt(refund, { cents: false })} will be returned to your pool.`
      : `Delete "${editing.name}"?`;
    if (!confirm(msg)) return;
    setState(s => {
      const next = { ...s, goals: s.goals.filter(x => x.id !== editing.id) };
      if (refund > 0) {
        next.pool = (s.pool || 0) + refund;
        next.poolLedger = [
          { id: 'p' + Date.now().toString(36), date: new Date().toISOString().slice(0, 10),
            amount: refund, kind: 'goal-withdraw', note: `Deleted goal: ${editing.name}` },
          ...(s.poolLedger || []),
        ];
      }
      return next;
    });
    requestClose();
  };

  return (
    <div className={`modal-backdrop ${sheetClass}`} onClick={requestClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-header">
          <div className="modal-sub">Savings pot</div>
          <div className="modal-title">{editing ? 'Edit savings goal' : 'New savings goal'}</div>
        </div>
        <div className="modal-body">
          <div className="field">
            <span className="field-label">Goal name</span>
            <input className="input" autoFocus value={name} onChange={e => setName(e.target.value)} placeholder="e.g. Iceland trip" />
          </div>
          <div className="row gap-sm">
            <div className="field" style={{ flex: 1 }}>
              <span className="field-label">Target amount</span>
              <div className="amount-wrap">
                <AmountInput value={target} onChange={setTarget} />
              </div>
            </div>
            <div className="field" style={{ flex: 1 }}>
              <span className="field-label">Monthly contribution</span>
              <div className="amount-wrap">
                <AmountInput value={monthly} onChange={setMonthly} />
              </div>
            </div>
          </div>
          <div className="field">
            <span className="field-label">Target date</span>
            <input className="input" type="date" value={deadline} onChange={e => setDeadline(e.target.value)} />
          </div>
          <div className="field">
            <span className="field-label">Color</span>
            <div className="color-grid">
              {Object.entries(TAG_COLORS).map(([key, hex]) => (
                <div key={key} className={`color-swatch ${color === key ? 'selected' : ''}`}
                  style={{ background: hex }} onClick={() => setColor(key)} />
              ))}
            </div>
          </div>
          <div className="field">
            <span className="field-label">Photo · optional</span>
            <div className="goal-photo-picker">
              <label
                className={`goal-photo-preview ${photo ? 'has-photo' : ''}`}
                style={photo ? { backgroundImage: `url(${photo})` } : null}
              >
                <input type="file" accept="image/*" style={{ display: 'none' }}
                  onChange={e => onPickPhoto(e.target.files?.[0])} />
                {!photo && (
                  <span className="goal-photo-icon" aria-hidden="true">
                    <svg viewBox="0 0 24 24" width="34" height="34" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                      <path d="M4 8h3l1.5-2h7L17 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/>
                      <circle cx="12" cy="13" r="3.5"/>
                    </svg>
                    <span className="goal-photo-hint">Add photo</span>
                  </span>
                )}
              </label>
              {photo && (
                <button type="button" className="btn ghost" onClick={() => setPhoto('')}>Remove</button>
              )}
            </div>
            {photoError && <div className="muted" style={{ fontSize: 11, color: 'var(--terracotta)', marginTop: 6 }}>{photoError}</div>}
          </div>
        </div>
        <div className="modal-footer">
          {editing && <button className="btn danger" onClick={remove} style={{ marginRight: 'auto' }}>Delete</button>}
          <button className="btn ghost" onClick={requestClose}>Cancel</button>
          <button className="btn primary" onClick={submit} disabled={!canSubmit}>{editing ? 'Save changes' : 'Create goal'}</button>
        </div>
      </div>
    </div>
  );
}

function ExtraIncomeModal({ state, setState, onClose, setActiveMonth }) {
  const { family } = state;
  const [requestClose, sheetClass] = useSheet(onClose);
  const [source, setSource] = React.useState('');
  const [amount, setAmount] = React.useState('');
  const [memberId, setMemberId] = React.useState(family[0]?.id || '');
  const todayIso = `${TODAY.getFullYear()}-${String(TODAY.getMonth()+1).padStart(2,'0')}-${String(TODAY.getDate()).padStart(2,'0')}`;
  const [date, setDate] = React.useState(todayIso);
  const [note, setNote] = React.useState('');

  const amountNum = parseFloat(amount);
  const canSubmit = source.trim() && amount && !isNaN(amountNum) && amountNum > 0;

  const submit = () => {
    if (!canSubmit) return;
    const x = {
      id: 'x' + Date.now().toString(36),
      source: source.trim(), amount: amountNum, memberId, date, note,
    };
    setState(s => ({ ...s, extra: [x, ...(s.extra || [])] }));
    if (setActiveMonth) setActiveMonth(monthKey(date));
    requestClose();
  };

  const onKeyDown = (ev) => { if (ev.key === 'Enter') submit(); };

  return (
    <div className={`modal-backdrop ${sheetClass}`} onClick={requestClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-header">
          <div className="modal-sub">Side income · gift · refund</div>
          <div className="modal-title">Log extra income</div>
        </div>
        <div className="modal-body">
          <div className="field">
            <span className="field-label">Amount</span>
            <div className="amount-wrap">
              <AmountInput value={amount} onChange={setAmount} autoFocus />
            </div>
          </div>
          <div className="field">
            <span className="field-label">Source</span>
            <input className="input" value={source} onChange={e => setSource(e.target.value)} onKeyDown={onKeyDown} placeholder="e.g. Freelance — logo refresh" />
          </div>
          <div className="row gap-sm">
            <div className="field" style={{ flex: 1 }}>
              <span className="field-label">Date received</span>
              <input className="input" type="date" value={date} onChange={e => setDate(e.target.value)} onKeyDown={onKeyDown} />
            </div>
            <div className="field" style={{ flex: 1 }}>
              <span className="field-label">Note (optional)</span>
              <input className="input" value={note} onChange={e => setNote(e.target.value)} onKeyDown={onKeyDown} placeholder="" />
            </div>
          </div>
          <div className="field">
            <span className="field-label">Earned by</span>
            <div className="member-picker">
              {family.map(m => (
                <button key={m.id} type="button"
                  className={`member-pick ${memberId === m.id ? 'selected' : ''}`}
                  onClick={() => setMemberId(m.id)}>
                  <span className="member-chip" style={{ background: TAG_COLORS[m.color] }}>{nameInitials(m.name)}</span>
                  <span>{m.name}</span>
                </button>
              ))}
            </div>
          </div>
        </div>
        <div className="modal-footer">
          <button className="btn ghost" onClick={requestClose}>Cancel</button>
          <button className="btn primary" onClick={submit} disabled={!canSubmit}>Log income</button>
        </div>
      </div>
    </div>
  );
}

function MemberModal({ state, setState, onClose }) {
  const { family } = state;
  const [requestClose, sheetClass] = useSheet(onClose);

  // Pick a default color that isn't already in use, falling back to ochre.
  const usedColors = new Set(family.map(m => m.color));
  const defaultColor =
    Object.keys(TAG_COLORS).find(c => !usedColors.has(c)) || 'ochre';

  const [name, setName]     = React.useState('');
  const [role, setRole]     = React.useState('partner');
  const [color, setColor]   = React.useState(defaultColor);
  const [budget, setBudget] = React.useState('');

  const submit = () => {
    const trimmed = name.trim();
    if (!trimmed) return;

    const initials = nameInitials(trimmed);
    const slug = trimmed.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'member';
    const id = `${slug}-${Date.now().toString(36).slice(-4)}`;

    const member = {
      id,
      name: trimmed,
      initials,
      color,
      budget: parseFloat(budget) || 0,
      role,
    };

    setState(s => ({ ...s, family: [...s.family, member] }));
    requestClose();
  };

  return (
    <div className={`modal-backdrop ${sheetClass}`} onClick={requestClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-header">
          <div className="modal-sub">New family member</div>
          <div className="modal-title">Add a member</div>
        </div>
        <div className="modal-body">
          <div className="field">
            <span className="field-label">Name</span>
            <input className="input" autoFocus
              value={name}
              onChange={e => setName(e.target.value)}
              onKeyDown={e => { if (e.key === 'Enter') submit(); }}
              placeholder="e.g. Zoë" />
          </div>
          <div className="field">
            <span className="field-label">Role</span>
            <select className="select" value={role} onChange={e => setRole(e.target.value)}>
              <option value="primary">Primary</option>
              <option value="partner">Partner</option>
              <option value="child">Child</option>
              <option value="shared">Shared</option>
              <option value="other">Other</option>
            </select>
          </div>
          <div className="field">
            <span className="field-label">Monthly budget · optional</span>
            <div className="amount-wrap">
              <AmountInput value={budget} onChange={setBudget} placeholder="0.00" />
            </div>
          </div>
          <div className="field">
            <span className="field-label">Color</span>
            <div className="color-grid">
              {Object.entries(TAG_COLORS).map(([key, hex]) => (
                <div key={key}
                  className={`color-swatch ${color === key ? 'selected' : ''}`}
                  style={{ background: hex }}
                  onClick={() => setColor(key)} />
              ))}
            </div>
          </div>
        </div>
        <div className="modal-footer">
          <button className="btn ghost" onClick={requestClose}>Cancel</button>
          <button className="btn primary" onClick={submit} disabled={!name.trim()}>Add member</button>
        </div>
      </div>
    </div>
  );
}

function TaskModal({ state, setState, editing, onClose }) {
  const { family, taskGroups = [] } = state;
  const [requestClose, sheetClass] = useSheet(onClose, 480);
  const isEdit = !!editing;
  const [title, setTitle]       = React.useState(editing?.title || '');
  const [dueDate, setDueDate]   = React.useState(editing?.dueDate || '');
  const [memberId, setMemberId] = React.useState(editing?.memberId || '');
  const [groupId, setGroupId]   = React.useState(editing?.groupId || '');
  const [creatingGroup, setCreatingGroup] = React.useState(false);
  const [newGroupName, setNewGroupName] = React.useState('');
  const [newGroupColor, setNewGroupColor] = React.useState('terracotta');
  const [recurrence, setRecurrence] = React.useState(editing?.recurrence?.type || 'none');
  const [interval, setInterval] = React.useState(editing?.recurrence?.interval || 1);
  const [priority, setPriority] = React.useState(editing?.priority || 'II');
  const [note, setNote]         = React.useState(editing?.note || '');

  const colorOptions = Object.keys(TAG_COLORS);

  // Live hint: what does the recurrence mean given the chosen due date?
  const recurrenceHint = React.useMemo(() => {
    if (recurrence === 'none' || !dueDate) return null;
    const n = Math.max(1, parseInt(interval, 10) || 1);
    const [y, m, d] = dueDate.split('-').map(Number);
    const date = new Date(y, m - 1, d);
    if (recurrence === 'daily') {
      return n === 1 ? 'Repeats every day' : `Repeats every ${n} days`;
    }
    if (recurrence === 'weekly') {
      const weekday = weekdayLong(dueDate);
      return n === 1 ? `Repeats every ${weekday}` : `Repeats every ${n} weeks on ${weekday}`;
    }
    if (recurrence === 'monthly') {
      const day = date.getDate();
      const suffix = day % 10 === 1 && day !== 11 ? 'st'
                   : day % 10 === 2 && day !== 12 ? 'nd'
                   : day % 10 === 3 && day !== 13 ? 'rd' : 'th';
      return n === 1
        ? `Repeats on the ${day}${suffix} of each month`
        : `Repeats every ${n} months on the ${day}${suffix}`;
    }
    return null;
  }, [recurrence, dueDate, interval]);

  const submit = () => {
    if (!title.trim()) return;
    let finalGroupId = groupId || null;
    setState(s => {
      let groups = s.taskGroups || [];
      if (creatingGroup && newGroupName.trim()) {
        const g = { id: 'tg' + Date.now().toString(36), name: newGroupName.trim(), color: newGroupColor };
        groups = [...groups, g];
        finalGroupId = g.id;
      }
      const recurrenceField = (() => {
        if (recurrence === 'none' || !dueDate) return null;
        const n = Math.max(1, parseInt(interval, 10) || 1);
        return n > 1 ? { type: recurrence, interval: n } : { type: recurrence };
      })();
      if (isEdit) {
        const tasks = (s.tasks || []).map(t => t.id === editing.id ? {
          ...t,
          title: title.trim(),
          dueDate: dueDate || null,
          memberId: memberId || null,
          groupId: finalGroupId,
          priority,
          note: note.trim(),
          recurrence: recurrenceField || undefined,
        } : t);
        return { ...s, taskGroups: groups, tasks };
      }
      const t = {
        id: 't' + Date.now().toString(36),
        title: title.trim(),
        dueDate: dueDate || null,
        memberId: memberId || null,
        groupId: finalGroupId,
        priority,
        note: note.trim(),
        done: false,
        subtasks: [],
      };
      if (recurrenceField) t.recurrence = recurrenceField;
      return { ...s, taskGroups: groups, tasks: [t, ...(s.tasks || [])] };
    });
    requestClose();
  };

  return (
    <div className={`modal-backdrop ${sheetClass}`} onClick={requestClose}>
      <div className="modal task-modal" onClick={e => e.stopPropagation()}>
        <div className="modal-header">
          <div className="modal-sub">{isEdit ? 'Edit task' : 'New task'}</div>
          <div className="modal-title">{isEdit ? (editing.title || 'Edit task') : 'Add a task'}</div>
        </div>
        <div className="modal-body">
          <div className="field">
            <span className="field-label">Title</span>
            <input className="input" autoFocus
              value={title}
              onChange={e => setTitle(e.target.value)}
              onKeyDown={e => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) submit(); }}
              placeholder="e.g. Combine accounts" />
          </div>
          <div className="field">
            <span className="field-label">Due date · optional</span>
            <input className="input" type="date"
              value={dueDate} onChange={e => setDueDate(e.target.value)} />
          </div>
          <div className="field">
            <span className="field-label">Priority</span>
            <div className="priority-row">
              {[
                { v: 'I',   label: 'I',   tone: 'low',  hint: 'Low' },
                { v: 'II',  label: 'II',  tone: 'med',  hint: 'Medium' },
                { v: 'III', label: 'III', tone: 'high', hint: 'High' },
              ].map(o => (
                <button key={o.v} type="button"
                  className={`priority-btn prio-${o.tone} ${priority === o.v ? 'active' : ''}`}
                  onClick={() => setPriority(o.v)}>
                  <span className="priority-btn-num">{o.label}</span>
                  <span className="priority-btn-hint">{o.hint}</span>
                </button>
              ))}
            </div>
          </div>
          <div className="field">
            <span className="field-label">Repeats · optional</span>
            <div className="recurrence-row">
              {[
                { v: 'none',    label: 'One-off' },
                { v: 'daily',   label: 'Daily' },
                { v: 'weekly',  label: 'Weekly' },
                { v: 'monthly', label: 'Monthly' },
              ].map(o => (
                <button key={o.v} type="button"
                  className={`recurrence-btn ${recurrence === o.v ? 'active' : ''}`}
                  disabled={o.v !== 'none' && !dueDate}
                  onClick={() => { setRecurrence(o.v); if (o.v === 'none') setInterval(1); }}>
                  {o.label}
                </button>
              ))}
            </div>
            {recurrence !== 'none' && dueDate && (
              <div className="recurrence-interval">
                <span className="recurrence-interval-label">Every</span>
                <input className="input recurrence-interval-input" type="number" min="1" max="99"
                  value={interval}
                  onChange={e => setInterval(e.target.value)}
                  onBlur={() => setInterval(Math.max(1, parseInt(interval, 10) || 1))} />
                <span className="recurrence-interval-label">
                  {recurrence === 'daily' ? 'day(s)' : recurrence === 'weekly' ? 'week(s)' : 'month(s)'}
                </span>
              </div>
            )}
            {recurrenceHint && <span className="field-hint">{recurrenceHint}</span>}
            {recurrence !== 'none' && !dueDate && (
              <span className="field-hint">Pick a due date first — it anchors the schedule.</span>
            )}
          </div>
          <div className="field">
            <span className="field-label">Assign to · optional</span>
            <select className="select" value={memberId} onChange={e => setMemberId(e.target.value)}>
              <option value="">Unassigned</option>
              {family.map(m => <option key={m.id} value={m.id}>{m.name}</option>)}
            </select>
          </div>
          <div className="field">
            <span className="field-label">Group · optional</span>
            {!creatingGroup ? (
              <div className="row gap-sm">
                <select className="select" style={{ flex: 1 }}
                  value={groupId} onChange={e => setGroupId(e.target.value)}>
                  <option value="">No group</option>
                  {taskGroups.map(g => <option key={g.id} value={g.id}>{g.name}</option>)}
                </select>
                <button className="btn ghost" onClick={() => { setCreatingGroup(true); setGroupId(''); }}>+ New group</button>
              </div>
            ) : (
              <div className="task-group-create">
                <input className="input" autoFocus
                  placeholder="e.g. Home, Car, Finance"
                  value={newGroupName}
                  onChange={e => setNewGroupName(e.target.value)} />
                <div className="task-group-color-row">
                  {colorOptions.map(c => (
                    <button key={c} type="button"
                      className={`task-group-swatch ${newGroupColor === c ? 'selected' : ''}`}
                      style={{ background: TAG_COLORS[c] }}
                      onClick={() => setNewGroupColor(c)}
                      title={c} />
                  ))}
                </div>
                <button className="btn ghost"
                  onClick={() => { setCreatingGroup(false); setNewGroupName(''); }}>
                  Cancel
                </button>
              </div>
            )}
          </div>
          <div className="field">
            <span className="field-label">Note · optional</span>
            <textarea className="input" rows={2}
              value={note}
              onChange={e => setNote(e.target.value)}
              placeholder="Any context — you'll add steps after this." />
          </div>
        </div>
        <div className="modal-footer">
          <button className="btn ghost" onClick={requestClose}>Cancel</button>
          <button className="btn primary" onClick={submit} disabled={!title.trim()}>{isEdit ? 'Save changes' : 'Add task'}</button>
        </div>
      </div>
    </div>
  );
}

function RecipeModal({ state, setState, editing, onClose }) {
  const [requestClose, sheetClass] = useSheet(onClose);
  const [name, setName]         = React.useState(editing?.name || '');
  const [mealTags, setMealTags] = React.useState(editing?.mealTags || []);
  const [tags, setTags]         = React.useState(editing?.tags || []);
  const [tagDraft, setTagDraft] = React.useState('');
  const [portions, setPortions] = React.useState(editing ? String(editing.portions || '') : '');
  const [leftovers, setLeftovers] = React.useState(editing ? String(editing.leftovers || '') : '');
  const [note, setNote]         = React.useState(editing?.note || '');
  const [ingredients, setIngredients] = React.useState(
    editing?.ingredients?.length
      ? editing.ingredients.map(i => ({
          purchaseCost: '', leftoverToPantry: false,
          ...i,
          // Strip legacy `inPantry` — pantry is now the source of truth, not the recipe.
          inPantry: undefined,
        }))
      : [{ name: '', qty: '', unit: '', estCost: '', purchaseCost: '', leftoverToPantry: false }]
  );

  // Pantry name set for "already in stock — won't be on shopping list" hint.
  const pantryInStock = React.useMemo(() =>
    new Set((state.pantry || [])
      .filter(p => !p.lowOnStock)
      .map(p => (p.name || '').toLowerCase().trim())),
    [state.pantry]
  );

  // Existing tags across all recipes — suggestions
  const allTags = React.useMemo(() => {
    const set = new Set();
    (state.recipes || []).forEach(r => (r.tags || []).forEach(t => set.add(t)));
    return [...set].sort();
  }, [state.recipes]);
  const suggestions = allTags.filter(t => !tags.includes(t)
    && (tagDraft.trim() === '' || t.toLowerCase().includes(tagDraft.trim().toLowerCase()))).slice(0, 6);

  const addTag = (raw) => {
    const v = (raw ?? tagDraft).trim().replace(/,$/, '').toLowerCase();
    if (!v) return;
    if (!tags.includes(v)) setTags([...tags, v]);
    setTagDraft('');
  };
  const removeTag = (t) => setTags(tags.filter(x => x !== t));

  // Recipes are evergreen — `total` is the full cost-to-cook regardless of pantry state.
  // Pantry-aware pricing happens at view time (RecipeCard, plan, etc.), not in the modal.
  const total = ingredients.reduce(
    (s, i) => s + (parseFloat(i.estCost) || 0),
    0
  );
  const tier = costTierIndex(total);
  const shoppingTotal = ingredients.reduce(
    (s, i) => s + (parseFloat(i.purchaseCost) || parseFloat(i.estCost) || 0),
    0
  );

  const updateIngredient = (idx, patch) => {
    setIngredients(list => list.map((it, i) => i === idx ? { ...it, ...patch } : it));
  };
  const blankIngredient = () => ({ name: '', qty: '', unit: '', estCost: '', purchaseCost: '', leftoverToPantry: false });
  const addIngredient = () => setIngredients(list => [...list, blankIngredient()]);
  const removeIngredient = (idx) => setIngredients(list => list.length === 1 ? [blankIngredient()] : list.filter((_, i) => i !== idx));

  const toggleMeal = (m) => setMealTags(tags => tags.includes(m) ? tags.filter(t => t !== m) : [...tags, m]);

  const canSubmit = name.trim().length > 0;

  const submit = () => {
    if (!canSubmit) return;
    const cleanIngredients = ingredients
      .filter(i => i.name.trim())
      .map(i => {
        const out = {
          name: i.name.trim(),
          qty: i.qty,
          unit: i.unit.trim(),
          estCost: parseFloat(i.estCost) || 0,
        };
        if (i.leftoverToPantry) {
          out.leftoverToPantry = true;
          const pc = parseFloat(i.purchaseCost);
          if (!isNaN(pc)) out.purchaseCost = pc;
        }
        return out;
      });
    const portionsNum = parseInt(portions) || 0;
    const leftoversNum = parseInt(leftovers) || 0;
    if (editing) {
      const updated = { ...editing, name: name.trim(), mealTags, tags, portions: portionsNum, leftovers: leftoversNum, ingredients: cleanIngredients, note: note.trim() };
      setState(s => ({ ...s, recipes: (s.recipes || []).map(r => r.id === editing.id ? updated : r) }));
    } else {
      const r = {
        id: 'r' + Date.now().toString(36),
        name: name.trim(), mealTags, tags,
        portions: portionsNum, leftovers: leftoversNum,
        ingredients: cleanIngredients, note: note.trim(),
      };
      setState(s => ({ ...s, recipes: [r, ...(s.recipes || [])] }));
    }
    requestClose();
  };

  const remove = () => {
    if (!editing) return;
    if (!confirm(`Delete recipe "${editing.name}"? Any planned meals using it will become empty slots.`)) return;
    setState(s => ({
      ...s,
      recipes: (s.recipes || []).filter(r => r.id !== editing.id),
      mealPlan: (s.mealPlan || []).filter(p => p.recipeId !== editing.id),
    }));
    requestClose();
  };

  return (
    <div className={`modal-backdrop ${sheetClass}`} onClick={requestClose}>
      <div className="modal recipe-modal" onClick={e => e.stopPropagation()}>
        <div className="modal-header">
          <div className="modal-sub">{editing ? 'Edit recipe' : 'New recipe'}</div>
          <div className="modal-title">{editing ? editing.name : 'Add a recipe'}</div>
        </div>
        <div className="modal-body">
          <div className="field">
            <span className="field-label">Name</span>
            <input className="input" autoFocus
              value={name} onChange={e => setName(e.target.value)}
              placeholder="e.g. Sheet-pan tacos" />
          </div>

          <div className="field">
            <span className="field-label">Served at</span>
            <div className="row gap-sm">
              {MEAL_TYPES.map(m => (
                <button key={m} type="button"
                  className={`seg ${mealTags.includes(m) ? 'active' : ''}`}
                  onClick={() => toggleMeal(m)}>{MEAL_TYPE_LABELS[m]}</button>
              ))}
            </div>
          </div>

          <div className="field">
            <span className="field-label">Tags · optional</span>
            <div className="tag-input-wrap">
              {tags.map(t => (
                <span key={t} className="recipe-tag-chip editable">
                  {t}
                  <button type="button" className="recipe-tag-x" onClick={() => removeTag(t)} title="Remove">×</button>
                </span>
              ))}
              <input className="tag-input"
                value={tagDraft}
                onChange={e => {
                  const v = e.target.value;
                  if (v.endsWith(',')) addTag(v.slice(0, -1));
                  else setTagDraft(v);
                }}
                onKeyDown={e => {
                  if (e.key === 'Enter') { e.preventDefault(); addTag(); }
                  else if (e.key === 'Backspace' && tagDraft === '' && tags.length) {
                    e.preventDefault(); setTags(tags.slice(0, -1));
                  }
                }}
                onBlur={() => addTag()}
                placeholder={tags.length ? '' : 'e.g. favorite, mexican, weeknight'} />
            </div>
            {suggestions.length > 0 && (
              <div className="tag-suggestions">
                {suggestions.map(s => (
                  <button key={s} type="button" className="tag-suggestion"
                    // preventDefault on mousedown keeps focus in the input so
                    // onBlur doesn't fire and add the half-typed draft.
                    onMouseDown={e => e.preventDefault()}
                    onClick={() => { setTagDraft(''); addTag(s); }}>+ {s}</button>
                ))}
              </div>
            )}
          </div>

          <div className="row gap-sm">
            <div className="field" style={{ flex: 1 }}>
              <span className="field-label">Portions</span>
              <input className="input" inputMode="numeric"
                value={portions} onChange={e => setPortions(e.target.value)}
                placeholder="e.g. 4" />
            </div>
            <div className="field" style={{ flex: 1 }}>
              <span className="field-label">Leftovers (extra portions)</span>
              <input className="input" inputMode="numeric"
                value={leftovers} onChange={e => setLeftovers(e.target.value)}
                placeholder="e.g. 2" />
            </div>
          </div>

          <div className="field">
            <div className="between" style={{ alignItems: 'baseline' }}>
              <span className="field-label">Ingredients</span>
              <span className="eyebrow num">
                cooks {fmt(total, { cents: false })} · price {COST_TIERS[tier]}
                {shoppingTotal !== total && <> · buy {fmt(shoppingTotal, { cents: false })}</>}
              </span>
            </div>
            <div className="ingredient-list">
              {ingredients.map((ing, idx) => {
                const inPantryNow = ing.name && pantryInStock.has(ing.name.trim().toLowerCase());
                return (
                  <div key={idx} className={`ingredient-row ${ing.leftoverToPantry ? 'has-bulk' : ''} ${inPantryNow ? 'in-pantry-now' : ''}`}>
                    <input className="input ing-name"
                      placeholder="Ingredient"
                      value={ing.name}
                      onChange={e => updateIngredient(idx, { name: e.target.value })} />
                    <input className="input ing-qty"
                      placeholder="Qty"
                      value={ing.qty}
                      onChange={e => updateIngredient(idx, { qty: e.target.value })} />
                    <input className="input ing-unit"
                      placeholder="Unit"
                      value={ing.unit}
                      onChange={e => updateIngredient(idx, { unit: e.target.value })} />
                    <input className="input ing-cost" inputMode="decimal"
                      placeholder="$ used"
                      value={ing.estCost}
                      onChange={e => updateIngredient(idx, { estCost: e.target.value })} />
                    <button type="button"
                      className={`ing-flag ${ing.leftoverToPantry ? 'on' : ''}`}
                      onClick={() => updateIngredient(idx, { leftoverToPantry: !ing.leftoverToPantry })}
                      title={ing.leftoverToPantry ? 'Bulk purchase — leftover stocks pantry' : 'Mark as bulk purchase (leftover stocks pantry)'}>B</button>
                    <button type="button" className="ing-remove" onClick={() => removeIngredient(idx)} title="Remove">×</button>
                    {ing.leftoverToPantry && (
                      <div className="ingredient-bulk-row">
                        <span className="ingredient-bulk-label">Bulk purchase</span>
                        <input className="input ing-bulk-cost" inputMode="decimal"
                          placeholder={`$ at store (defaults to ${ing.estCost ? '$' + ing.estCost : '$ used'})`}
                          value={ing.purchaseCost}
                          onChange={e => updateIngredient(idx, { purchaseCost: e.target.value })} />
                        <span className="ingredient-bulk-hint muted">leftover stocks pantry</span>
                      </div>
                    )}
                    {inPantryNow && (
                      <div className="ingredient-pantry-hint">
                        <span className="muted">In your pantry — won't be on the shopping list.</span>
                      </div>
                    )}
                  </div>
                );
              })}
              <button type="button" className="btn ghost" style={{ marginTop: 8 }} onClick={addIngredient}>+ Add ingredient</button>
            </div>
            <div className="muted mono" style={{ fontSize: 10.5, marginTop: 8, lineHeight: 1.6 }}>
              <b>B</b> = bulk purchase (recipe uses a little, leftover stocks pantry on checkoff). Anything you have in your pantry is already skipped — manage staples on the Pantry tab.
            </div>
          </div>

          <div className="field">
            <span className="field-label">Instructions &middot; optional</span>
            <textarea className="input recipe-instructions-input" rows={8}
              value={note}
              onChange={e => setNote(e.target.value)}
              placeholder={"Step-by-step cooking instructions, source link, family rating, anything you'd want to glance at while cooking.\n\nLine breaks and blank lines are preserved when you read this back."} />
            <div className="muted mono" style={{ fontSize: 10.5, marginTop: 6 }}>
              Plain text — line breaks are kept when reading.
            </div>
          </div>
        </div>
        <div className="modal-footer">
          {editing && <button className="btn danger" style={{ marginRight: 'auto' }} onClick={remove}>Delete</button>}
          <button className="btn ghost" onClick={requestClose}>Cancel</button>
          <button className="btn primary" onClick={submit} disabled={!canSubmit}>{editing ? 'Save changes' : 'Add recipe'}</button>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { ExpenseModal, FixedModal, GoalModal, ExtraIncomeModal, TaskModal, MemberModal, RecipeModal });
