// Design Rebuild view - LLD / HLD / Go concurrency spaced repetition.
// Question templates come from the repo bank (data/design_rebuild_question_bank.json),
// are synced into D1, and everything rendered here is read back from D1 so the
// templates and the logged answers always stay consistent.

const DESIGN_SRS_BANK_URL = 'data/design_rebuild_question_bank.json';

const DESIGN_SRS_INTERVALS = [0, 1, 3, 7, 14, 30, 60, 120];

const DESIGN_SRS_DEFAULT_SETTINGS = {
  dailyBudgetMinutes: 60,
  focusCount: 3,
  intervals: DESIGN_SRS_INTERVALS,
};

const DESIGN_SRS_WEIGHT_RANK = { core: 0, high: 1, medium: 2, supporting: 3 };

const DESIGN_SRS_CATEGORY_LABELS = {
  go_concurrency: 'Go Concurrency',
  lld_pattern: 'LLD Patterns',
  lld_pattern_foundation: 'Pattern Foundations',
  lld_system: 'LLD Interview Systems',
  lld_practice: 'LLD Engineering Practices',
  lld_principle: 'SOLID Principles',
  lld_foundation: 'LLD Foundations',
  lld_visual_model: 'UML & Diagrams',
  hld: 'HLD Chapters',
  hld_mock: 'HLD Mocks',
};

const DESIGN_SRS_MISSIONS = [
  { label: 'D0', mission: 'First rebuild: produce the artifact from memory right after learning or relearning.' },
  { label: 'D1', mission: 'Blind recall without notes: rebuild the core structure and answer the prompts cold.' },
  { label: 'D3', mission: 'Apply a variation: change one requirement and adapt the design out loud.' },
  { label: 'D7', mission: 'Compare: contrast this topic with its closest related patterns or systems.' },
  { label: 'D14', mission: 'Interview-style verbal defense: answer the prompts as if the interviewer is pushing back.' },
  { label: 'D30', mission: 'Long-retention mock answer: full answer under time pressure, no warm-up.' },
  { label: 'D60', mission: 'Long-retention mock answer: full answer under time pressure, no warm-up.' },
  { label: 'D120', mission: 'Final retention check: deliver the complete mock answer and log the gaps.' },
];

function designSrsTodayString() {
  return fmtDate(nowInIST());
}

function designSrsClampStage(stage) {
  return Math.max(0, Math.min(DESIGN_SRS_INTERVALS.length - 1, Number(stage || 0)));
}

function designSrsMission(stage) {
  return DESIGN_SRS_MISSIONS[designSrsClampStage(stage)];
}

function designSrsOccurrenceText(stage) {
  const idx = designSrsClampStage(stage);
  return `${DESIGN_SRS_MISSIONS[idx].label} - occurrence ${idx + 1}/${DESIGN_SRS_INTERVALS.length}`;
}

function designSrsCategoryLabel(category) {
  return DESIGN_SRS_CATEGORY_LABELS[category] || (category || 'Uncategorized');
}

function designSrsWeightRank(weight) {
  const rank = DESIGN_SRS_WEIGHT_RANK[String(weight || '').toLowerCase()];
  return rank === undefined ? 2 : rank;
}

function designSrsEstimatedMinutes(topic) {
  return Number(topic?.stage || 0) === 0 ? 25 : 12;
}

// Must stay byte-identical with designSrsFingerprint in functions/api/design-srs/_shared.js.
function designSrsFingerprint(text) {
  let hash = 5381;
  const value = String(text || '');
  for (let i = 0; i < value.length; i += 1) {
    hash = ((hash * 33) ^ value.charCodeAt(i)) >>> 0;
  }
  return `fp_${hash.toString(16)}_${value.length}`;
}

function designSrsSortTopics(topics) {
  return [...(topics || [])].sort((a, b) => {
    const ad = a.dueDate || a.firstRebuildDate || '9999-12-31';
    const bd = b.dueDate || b.firstRebuildDate || '9999-12-31';
    if (ad !== bd) return ad.localeCompare(bd);
    const aw = designSrsWeightRank(a.interviewWeight);
    const bw = designSrsWeightRank(b.interviewWeight);
    if (aw !== bw) return aw - bw;
    return String(a.title || '').localeCompare(String(b.title || ''));
  });
}

function designSrsFocusSort(topics) {
  return [...(topics || [])].sort((a, b) => {
    const aw = designSrsWeightRank(a.interviewWeight);
    const bw = designSrsWeightRank(b.interviewWeight);
    if (aw !== bw) return aw - bw;
    const ad = a.dueDate || '9999-12-31';
    const bd = b.dueDate || '9999-12-31';
    if (ad !== bd) return ad.localeCompare(bd);
    return String(a.title || '').localeCompare(String(b.title || ''));
  });
}

function designSrsAttemptsByTopic(attempts) {
  const grouped = {};
  (attempts || []).forEach(attempt => {
    if (!grouped[attempt.topicId]) grouped[attempt.topicId] = [];
    grouped[attempt.topicId].push(attempt);
  });
  Object.values(grouped).forEach(list => {
    list.sort((a, b) => String(b.createdAt || '').localeCompare(String(a.createdAt || '')));
  });
  return grouped;
}

function designSrsIsPendingAttempt(attempt) {
  return attempt?.mode === 'keep_pending' || attempt?.payload?.pending;
}

function designSrsLatestAttempt(attemptsByTopic, topicId) {
  return (attemptsByTopic?.[topicId] || [])[0] || null;
}

function designSrsTopicById(topics) {
  const map = {};
  (topics || []).forEach(topic => { map[topic.id] = topic; });
  return map;
}

function designSrsDatePart(value) {
  return String(value || '').slice(0, 10);
}

function designSrsRatingLabel(rating) {
  if (rating === 'strong') return 'strong';
  if (rating === 'medium') return 'shaky';
  if (rating === 'weak') return 'weak';
  return rating || '-';
}

function designSrsRatingTone(rating) {
  if (rating === 'strong') return 'accent';
  if (rating === 'weak') return 'warn';
  return '';
}

function designSrsActiveTopics(topics) {
  return (topics || []).filter(topic => topic.status !== 'archived');
}

function designSrsDueTopics(topics, attemptsByTopic, today) {
  return designSrsSortTopics(designSrsActiveTopics(topics).filter(topic => {
    if (!topic.dueDate || topic.dueDate > today) return false;
    return !designSrsIsPendingAttempt(designSrsLatestAttempt(attemptsByTopic, topic.id));
  }));
}

function designSrsParkedTopics(topics, attemptsByTopic) {
  return designSrsSortTopics(designSrsActiveTopics(topics).filter(topic => (
    designSrsIsPendingAttempt(designSrsLatestAttempt(attemptsByTopic, topic.id))
  )));
}

function DesignSrsPill({ children, tone = '' }) {
  return <span className={`pill ${tone}`}>{children}</span>;
}

function DesignSrsTabs({ tab, setTab, dueCount = 0, backlogCount = 0 }) {
  const tabs = [
    ['today', dueCount ? `Today ${dueCount}` : 'Today'],
    ['backlog', backlogCount ? `Backlog ${backlogCount}` : 'Backlog'],
    ['calendar', 'Calendar'],
    ['roadmap', 'Roadmap'],
    ['library', 'Library'],
    ['mocks', 'Mocks'],
    ['settings', 'Settings'],
  ];
  return (
    <div className="dsa-srs-tabs">
      {tabs.map(([id, label]) => (
        <button key={id} className={`btn ${tab === id ? 'primary' : ''}`} onClick={() => setTab(id)}>
          {label}
        </button>
      ))}
    </div>
  );
}

function useDesignSrsData() {
  const [snapshot, setSnapshot] = React.useState({
    settings: DESIGN_SRS_DEFAULT_SETTINGS,
    bankSync: null,
    topics: [],
    attempts: [],
    loading: true,
    error: '',
  });
  const [bankNotice, setBankNotice] = React.useState('');
  const [syncing, setSyncing] = React.useState(false);
  const autoSyncDone = React.useRef(false);

  const applySnapshot = React.useCallback((data) => {
    setSnapshot({
      settings: { ...DESIGN_SRS_DEFAULT_SETTINGS, ...(data.settings || {}) },
      bankSync: data.bankSync || null,
      topics: Array.isArray(data.topics) ? data.topics : [],
      attempts: Array.isArray(data.attempts) ? data.attempts : [],
      loading: false,
      error: '',
    });
  }, []);

  const load = React.useCallback(async ({ silent = false } = {}) => {
    if (!silent) setSnapshot(prev => ({ ...prev, loading: true, error: '' }));
    try {
      const response = await fetch('/api/design-srs/state', { credentials: 'include' });
      const data = await response.json();
      if (!response.ok || data.error) throw new Error(data.error || 'Could not load Design SRS.');
      applySnapshot(data);
      return data;
    } catch (error) {
      setSnapshot(prev => ({ ...prev, loading: false, error: error.message || String(error) }));
      return null;
    }
  }, [applySnapshot]);

  const post = React.useCallback(async (path, body) => {
    setSnapshot(prev => ({ ...prev, error: '' }));
    const response = await fetch(path, {
      method: 'POST',
      credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body || {}),
    });
    const data = await response.json();
    if (!response.ok || data.error) throw new Error(data.error || 'Design SRS request failed.');
    applySnapshot(data);
    return data;
  }, [applySnapshot]);

  // Bank -> D1 sync. Fetches the repo bank file, fingerprints it, and only
  // POSTs when D1 is out of date. Every displayed topic still comes from D1.
  const syncBank = React.useCallback(async ({ force = false, currentSync = null } = {}) => {
    setSyncing(true);
    setBankNotice('');
    try {
      let bankText = '';
      try {
        const bankResponse = await fetch(`${DESIGN_SRS_BANK_URL}?t=${Date.now()}`, { cache: 'no-store' });
        if (!bankResponse.ok) throw new Error(`HTTP ${bankResponse.status}`);
        bankText = await bankResponse.text();
      } catch (error) {
        setBankNotice(`Question bank file could not be fetched (${error.message || error}). Topics and answers are still served from D1.`);
        return null;
      }

      let bank;
      try {
        bank = JSON.parse(bankText);
      } catch (_) {
        setBankNotice('Question bank file is not valid JSON. Fix data/design_rebuild_question_bank.json; D1 data is untouched.');
        return null;
      }

      const fingerprint = designSrsFingerprint(JSON.stringify(bank));
      if (!force && currentSync?.fingerprint === fingerprint) {
        return { unchanged: true };
      }

      try {
        const data = await post('/api/design-srs/sync', { bank });
        return data.sync || null;
      } catch (error) {
        setBankNotice(`Bank sync failed: ${error.message || error}. Existing D1 topics and answers are untouched.`);
        return null;
      }
    } finally {
      setSyncing(false);
    }
  }, [post]);

  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      const data = await load();
      if (cancelled || !data || autoSyncDone.current) return;
      autoSyncDone.current = true;
      await syncBank({ currentSync: data.bankSync });
    })();
    return () => { cancelled = true; };
  }, [load, syncBank]);

  return { snapshot, applySnapshot, load, post, syncBank, syncing, bankNotice, setBankNotice };
}

function DesignSrsQuestionBlock({ question, index, answer, onAnswerChange, readOnly = false }) {
  const [showHints, setShowHints] = React.useState(false);
  const [writing, setWriting] = React.useState(!!answer);

  React.useEffect(() => {
    if (answer && !writing) setWriting(true);
  }, [answer, writing]);

  return (
    <div className="dsa-srs-note-box" style={{display:'grid', gap:'8px'}}>
      <div className="dsa-srs-pill-row">
        <DesignSrsPill>{`Q${index + 1}`}</DesignSrsPill>
        <DesignSrsPill>{question.type || 'recall'}</DesignSrsPill>
        {!!(question.expectedEvidence || []).length && (
          <button className="btn tiny" type="button" onClick={() => setShowHints(v => !v)}>
            {showHints ? 'Hide checklist' : 'Answer checklist'}
          </button>
        )}
        {!readOnly && (
          <button className="btn tiny" type="button" onClick={() => setWriting(v => !v)}>
            {writing ? 'Hide answer box' : 'Write answer'}
          </button>
        )}
      </div>
      <div>{question.prompt}</div>
      {showHints && (
        <div className="mono muted" style={{fontSize:'11px'}}>
          Cover: {(question.expectedEvidence || []).join(' · ')}
        </div>
      )}
      {!readOnly && writing && (
        <textarea
          className="textarea"
          value={answer || ''}
          onChange={e => onAnswerChange(question.id, e.target.value)}
          placeholder="Answer from memory. This is stored in D1 with this occurrence."
          style={{minHeight:'80px'}}
        />
      )}
    </div>
  );
}

function DesignSrsReviewCard({ topic, onAttempt, onKeptPending, showPendingAction = true, focus = false }) {
  const [answers, setAnswers] = React.useState({});
  const [artifactText, setArtifactText] = React.useState('');
  const [notes, setNotes] = React.useState('');
  const [rating, setRating] = React.useState('');
  const [saving, setSaving] = React.useState(false);
  const mission = designSrsMission(topic.stage);
  const questions = Array.isArray(topic.questions) ? topic.questions : [];

  function setAnswer(questionId, text) {
    setAnswers(prev => ({ ...prev, [questionId]: text }));
  }

  function resetDraft() {
    setAnswers({});
    setArtifactText('');
    setNotes('');
    setRating('');
  }

  async function submit() {
    if (!rating) return;
    setSaving(true);
    try {
      await onAttempt(topic.id, {
        answers,
        artifactText,
        notes,
        rating,
        mode: 'review',
        reviewDate: designSrsTodayString(),
      });
      resetDraft();
    } catch (error) {
      alert(error.message || String(error));
    } finally {
      setSaving(false);
    }
  }

  async function keepPending() {
    setSaving(true);
    try {
      await onAttempt(topic.id, {
        answers,
        artifactText,
        notes,
        mode: 'keep_pending',
        reviewDate: designSrsTodayString(),
      });
      onKeptPending?.();
      resetDraft();
    } catch (error) {
      alert(error.message || String(error));
    } finally {
      setSaving(false);
    }
  }

  return (
    <div className="card dsa-srs-review-card" style={focus ? {borderColor:'var(--accent)'} : undefined}>
      <div className="dsa-srs-review-head">
        <div style={{minWidth:0}}>
          <div className="dsa-srs-pill-row">
            {focus && <DesignSrsPill tone="accent">focus</DesignSrsPill>}
            <DesignSrsPill tone="accent">{designSrsCategoryLabel(topic.category)}</DesignSrsPill>
            <DesignSrsPill>{topic.interviewWeight || 'medium'}</DesignSrsPill>
            <DesignSrsPill>due {topic.dueDate || 'today'}</DesignSrsPill>
            <DesignSrsPill>{designSrsOccurrenceText(topic.stage)}</DesignSrsPill>
            {topic.weak && <DesignSrsPill tone="warn">weak - repair pass</DesignSrsPill>}
          </div>
          <h3 className="dsa-srs-problem-title">{topic.title}</h3>
        </div>
        <div className="mono muted dsa-srs-attempt-count">{topic.attemptCount || 0} reviews</div>
      </div>

      <div className="dsa-srs-mission">
        <div className="kicker" style={{margin:0}}>{mission.label} mission</div>
        <div className="muted">{mission.mission}</div>
      </div>

      {!!topic.sameDayArtifact && (
        <div className="dsa-srs-note-box">
          <strong>Artifact:</strong> {topic.sameDayArtifact}
        </div>
      )}

      <textarea
        className="textarea"
        value={artifactText}
        onChange={e => setArtifactText(e.target.value)}
        placeholder="Artifact log: what you rebuilt/drew/coded from memory for this occurrence."
        style={{minHeight:'90px'}}
      />

      {questions.length ? (
        <div style={{display:'grid', gap:'10px'}}>
          {questions.map((question, index) => (
            <DesignSrsQuestionBlock
              key={question.id || index}
              question={question}
              index={index}
              answer={answers[question.id] || ''}
              onAnswerChange={setAnswer}
            />
          ))}
        </div>
      ) : (
        <div className="dsa-srs-note-box muted">
          This topic has no questions in the bank. Re-sync from Settings after fixing the bank file.
        </div>
      )}

      <input
        className="input"
        value={notes}
        onChange={e => setNotes(e.target.value)}
        placeholder="Optional weak point / miss to tag for the next occurrence..."
      />

      <div className="dsa-srs-complete-row" style={{flexWrap:'wrap'}}>
        <div className="dsa-srs-pill-row">
          {[['strong', 'Strong'], ['medium', 'Shaky'], ['weak', 'Weak']].map(([value, label]) => (
            <button
              key={value}
              type="button"
              className={`btn ${rating === value ? 'primary' : ''}`}
              onClick={() => setRating(prev => (prev === value ? '' : value))}
            >
              {label}
            </button>
          ))}
        </div>
        <button className="btn primary" disabled={saving || !rating} onClick={submit}>
          {saving ? 'Saving...' : 'Complete review'}
        </button>
        {showPendingAction && (
          <button className="btn" disabled={saving} onClick={keepPending}>
            Keep pending
          </button>
        )}
      </div>
      <div className="mono muted" style={{fontSize:'11px'}}>
        Strong advances the D0/D1/D3/D7/D14/D30/D60/D120 ladder. Shaky advances at half interval. Weak repeats this occurrence tomorrow as a repair pass. Keep pending stores your notes without advancing.
      </div>
    </div>
  );
}

function DesignSrsAttemptEntry({ attempt, topic, onEditAttempt, onUndoAttempt, undoableAttemptId = '' }) {
  const [editing, setEditing] = React.useState(false);
  const [draft, setDraft] = React.useState(() => ({
    artifactText: attempt.artifactText || '',
    notes: attempt.notes || '',
    answers: { ...(attempt.answers || {}) },
  }));
  const [saving, setSaving] = React.useState(false);
  const pending = designSrsIsPendingAttempt(attempt);
  const questions = Array.isArray(topic?.questions) ? topic.questions : [];
  const questionById = {};
  questions.forEach(question => { questionById[question.id] = question; });
  const beforeStage = attempt?.payload?.before?.stage;
  const occurrence = pending
    ? `pending from ${attempt?.payload?.before?.dueDate || attempt.dueDate || '-'}`
    : designSrsOccurrenceText(Number.isFinite(Number(beforeStage)) ? Number(beforeStage) : Math.max(0, Number(attempt.stage || 1) - 1));

  React.useEffect(() => {
    if (!editing) {
      setDraft({
        artifactText: attempt.artifactText || '',
        notes: attempt.notes || '',
        answers: { ...(attempt.answers || {}) },
      });
    }
  }, [attempt.artifactText, attempt.notes, attempt.answers, editing]);

  async function saveEdit() {
    setSaving(true);
    try {
      await onEditAttempt(attempt.id, draft);
      setEditing(false);
    } catch (error) {
      alert(error.message || String(error));
    } finally {
      setSaving(false);
    }
  }

  async function undoAttempt() {
    if (!confirm('Undo this completed review and restore the topic to its previous occurrence?')) return;
    setSaving(true);
    try {
      await onUndoAttempt(attempt.id);
    } catch (error) {
      alert(error.message || String(error));
    } finally {
      setSaving(false);
    }
  }

  const answerEntries = Object.entries(attempt.answers || {});

  return (
    <div className="dsa-srs-attempt-entry">
      <div className="dsa-srs-attempt-entry-head">
        <span className="mono">{occurrence}</span>
        <span className="mono muted">{designSrsDatePart(attempt.reviewDate || attempt.createdAt)}</span>
        <DesignSrsPill tone={pending ? 'warn' : designSrsRatingTone(attempt.rating)}>
          {pending ? 'pending' : designSrsRatingLabel(attempt.rating)}
        </DesignSrsPill>
        {!!answerEntries.length && <DesignSrsPill>{answerEntries.length} answers</DesignSrsPill>}
        {onEditAttempt && (
          <button className="btn tiny" type="button" onClick={() => setEditing(v => !v)}>
            {editing ? 'Cancel' : 'Edit'}
          </button>
        )}
        {onUndoAttempt && attempt.id === undoableAttemptId && !pending && (
          <button className="btn tiny ghost" type="button" disabled={saving} onClick={undoAttempt}>
            Undo
          </button>
        )}
      </div>

      {editing ? (
        <div className="dsa-srs-attempt-edit">
          <textarea
            className="textarea"
            value={draft.artifactText}
            onChange={e => setDraft(prev => ({ ...prev, artifactText: e.target.value }))}
            placeholder="Correct the saved artifact log."
            style={{minHeight:'80px'}}
          />
          {questions.map(question => (
            <div key={question.id} style={{display:'grid', gap:'4px'}}>
              <div className="mono muted" style={{fontSize:'11px'}}>{question.prompt}</div>
              <textarea
                className="textarea"
                value={draft.answers[question.id] || ''}
                onChange={e => setDraft(prev => ({ ...prev, answers: { ...prev.answers, [question.id]: e.target.value } }))}
                style={{minHeight:'60px'}}
              />
            </div>
          ))}
          <input
            className="input"
            value={draft.notes}
            onChange={e => setDraft(prev => ({ ...prev, notes: e.target.value }))}
            placeholder="Correct the optional note."
          />
          <div className="dsa-srs-complete-row">
            <button className="btn primary" type="button" disabled={saving} onClick={saveEdit}>
              {saving ? 'Saving...' : 'Save edit'}
            </button>
          </div>
        </div>
      ) : (
        <div className="dsa-srs-note-box" style={{display:'grid', gap:'6px'}}>
          {attempt.artifactText && <div><strong>Artifact:</strong> {attempt.artifactText}</div>}
          {answerEntries.map(([questionId, text]) => (
            <div key={questionId}>
              <strong>{questionById[questionId]?.prompt || `${questionId} (question no longer in bank)`}</strong>
              <div style={{whiteSpace:'pre-wrap'}}>{text}</div>
            </div>
          ))}
          {attempt.notes && <div><strong>Note:</strong> {attempt.notes}</div>}
          {!attempt.artifactText && !answerEntries.length && !attempt.notes && (
            <span className="muted">No artifact text or answers were saved for this occurrence.</span>
          )}
        </div>
      )}

      {!pending && (
        <div className="mono muted dsa-srs-attempt-next">
          Next after this: {designSrsOccurrenceText(attempt.stage)} due {attempt.dueDate || '-'}
        </div>
      )}
    </div>
  );
}

function DesignSrsAttemptTimeline({ attempts, topic, onEditAttempt, onUndoAttempt, undoableAttemptId = '' }) {
  const ordered = [...(attempts || [])].sort((a, b) => String(a.createdAt || '').localeCompare(String(b.createdAt || '')));
  if (!ordered.length) return null;
  return (
    <div className="dsa-srs-attempt-timeline">
      {ordered.map((attempt, index) => (
        <DesignSrsAttemptEntry
          key={attempt.id || index}
          attempt={attempt}
          topic={topic}
          onEditAttempt={onEditAttempt}
          onUndoAttempt={onUndoAttempt}
          undoableAttemptId={undoableAttemptId}
        />
      ))}
    </div>
  );
}

function DesignSrsDueDateControl({ topic, onReschedule, label = 'Move' }) {
  const [date, setDate] = React.useState(topic?.dueDate || topic?.firstRebuildDate || '');
  const [saving, setSaving] = React.useState(false);

  React.useEffect(() => {
    setDate(topic?.dueDate || topic?.firstRebuildDate || '');
  }, [topic?.dueDate, topic?.firstRebuildDate]);

  async function submit(event) {
    event?.preventDefault?.();
    if (!date || date === topic?.dueDate) return;
    setSaving(true);
    try {
      await onReschedule(topic.id, date);
    } catch (error) {
      alert(error.message || String(error));
    } finally {
      setSaving(false);
    }
  }

  return (
    <form className="dsa-srs-reschedule-control" onSubmit={submit}>
      <input
        className="input"
        type="date"
        value={date}
        aria-label={`Due date for ${topic?.title || 'topic'}`}
        onChange={event => setDate(event.target.value)}
      />
      <button className="btn" disabled={saving || !date || date === topic?.dueDate}>
        {saving ? 'Moving...' : label}
      </button>
    </form>
  );
}

function DesignSrsModalPortal({ children }) {
  if (typeof document === 'undefined' || typeof ReactDOM === 'undefined' || !ReactDOM.createPortal) {
    return children;
  }
  return ReactDOM.createPortal(children, document.body);
}

function DesignSrsTopicDetailModal({ topic, attempts = [], onClose, onReschedule, onEditAttempt, onUndoAttempt, onFlags }) {
  const [showEvidence, setShowEvidence] = React.useState(false);
  if (!topic) return null;
  const questions = Array.isArray(topic.questions) ? topic.questions : [];
  const latest = (attempts || [])[0] || null;
  const undoableId = latest && !designSrsIsPendingAttempt(latest) ? latest.id : '';

  return (
    <DesignSrsModalPortal>
      <div className="modal-bg" onClick={onClose}>
        <div className="modal dsa-srs-problem-modal" onClick={event => event.stopPropagation()}>
          <div className="dsa-srs-problem-modal-head">
            <div style={{minWidth:0}}>
              <div className="dsa-srs-pill-row">
                <DesignSrsPill tone="accent">{designSrsCategoryLabel(topic.category)}</DesignSrsPill>
                <DesignSrsPill>{topic.interviewWeight || 'medium'}</DesignSrsPill>
                <DesignSrsPill>{designSrsOccurrenceText(topic.stage)}</DesignSrsPill>
                {topic.weak && <DesignSrsPill tone="warn">weak</DesignSrsPill>}
                {topic.status === 'archived' && <DesignSrsPill tone="warn">archived</DesignSrsPill>}
              </div>
              <div className="dsa-srs-modal-title">{topic.title}</div>
            </div>
            <button className="btn ghost" onClick={onClose}>Close</button>
          </div>

          <div className="dsa-srs-detail-grid">
            <Stat label="Due date" value={topic.dueDate || '-'} sub="current occurrence" />
            <Stat label="Reviews" value={topic.attemptCount || 0} sub={topic.lastRating ? `last: ${designSrsRatingLabel(topic.lastRating)}` : 'not started'} accent={topic.attemptCount > 0} />
            <Stat label="Queue date" value={topic.firstRebuildDate || '-'} sub="bank seed date" />
          </div>

          {!!topic.priorityReason && (
            <div className="dsa-srs-note-box"><strong>Why it matters:</strong> {topic.priorityReason}</div>
          )}
          {!!topic.sameDayArtifact && (
            <div className="dsa-srs-note-box"><strong>Artifact:</strong> {topic.sameDayArtifact}</div>
          )}

          <div className="dsa-srs-modal-section">
            <div className="dsa-srs-pill-row" style={{justifyContent:'space-between'}}>
              <div className="kicker">Question template ({questions.length}) - from the repo bank via D1</div>
              <button className="btn tiny" onClick={() => setShowEvidence(v => !v)}>
                {showEvidence ? 'Hide checklists' : 'Show checklists'}
              </button>
            </div>
            {questions.length ? questions.map((question, index) => (
              <div key={question.id || index} className="dsa-srs-note-box" style={{display:'grid', gap:'6px'}}>
                <div className="dsa-srs-pill-row">
                  <DesignSrsPill>{`Q${index + 1}`}</DesignSrsPill>
                  <DesignSrsPill>{question.type || 'recall'}</DesignSrsPill>
                </div>
                <div>{question.prompt}</div>
                {showEvidence && !!(question.expectedEvidence || []).length && (
                  <div className="mono muted" style={{fontSize:'11px'}}>
                    Cover: {(question.expectedEvidence || []).join(' · ')}
                  </div>
                )}
              </div>
            )) : (
              <div className="dsa-srs-note-box muted">No questions synced for this topic. Fix the bank file and re-sync.</div>
            )}
          </div>

          {(onReschedule || onFlags) && topic.status !== 'archived' && (
            <div className="dsa-srs-modal-control">
              <div>
                <div className="kicker">Controls</div>
                <div className="muted">Moving the due date updates Today, Backlog, Calendar, and Roadmap from D1.</div>
              </div>
              <div className="dsa-srs-pill-row" style={{alignItems:'center'}}>
                {onReschedule && <DesignSrsDueDateControl topic={topic} onReschedule={onReschedule} label="Move due" />}
                {onFlags && (
                  <button className="btn" onClick={() => onFlags(topic.id, { weak: !topic.weak })}>
                    {topic.weak ? 'Clear weak tag' : 'Tag as weak'}
                  </button>
                )}
                {onFlags && (
                  <button className="btn ghost" onClick={() => {
                    if (confirm('Archive this topic? History stays in D1 and it can be restored from Settings.')) {
                      onFlags(topic.id, { status: 'archived' });
                    }
                  }}>
                    Archive
                  </button>
                )}
              </div>
            </div>
          )}
          {onFlags && topic.status === 'archived' && (
            <div className="dsa-srs-modal-control">
              <div>
                <div className="kicker">Archived topic</div>
                <div className="muted">It is hidden from Today/Backlog but its answer history is intact.</div>
              </div>
              <button className="btn primary" onClick={() => onFlags(topic.id, { status: 'active' })}>Restore</button>
            </div>
          )}

          <div className="dsa-srs-modal-section">
            <div className="kicker">Answer history (from D1)</div>
            {attempts.length ? (
              <DesignSrsAttemptTimeline
                attempts={attempts}
                topic={topic}
                onEditAttempt={onEditAttempt}
                onUndoAttempt={onUndoAttempt}
                undoableAttemptId={undoableId}
              />
            ) : (
              <div className="dsa-srs-note-box muted">
                No occurrences logged yet. Once you complete this topic from Today, every artifact, answer, and note lands here.
              </div>
            )}
          </div>
        </div>
      </div>
    </DesignSrsModalPortal>
  );
}

function DesignSrsTopicRow({ topic, attempts = [], onOpen, rightSlot, today }) {
  return (
    <div className="dsa-srs-problem-row compact">
      <button className="dsa-srs-problem-open" onClick={() => onOpen(topic)}>
        <div className="dsa-srs-pill-row">
          <DesignSrsPill>{topic.interviewWeight || 'medium'}</DesignSrsPill>
          <DesignSrsPill tone={topic.dueDate && topic.dueDate <= today ? 'warn' : ''}>due {topic.dueDate || '-'}</DesignSrsPill>
          <DesignSrsPill>{designSrsOccurrenceText(topic.stage)}</DesignSrsPill>
          {topic.attemptCount > 0 && <DesignSrsPill tone="accent">{topic.attemptCount} reviewed</DesignSrsPill>}
          {topic.weak && <DesignSrsPill tone="warn">weak</DesignSrsPill>}
          {topic.status === 'archived' && <DesignSrsPill tone="warn">archived</DesignSrsPill>}
        </div>
        <div className="dsa-srs-bank-title">{topic.title}</div>
      </button>
      {rightSlot}
    </div>
  );
}

function DesignSrsTodayTab({ topics, attempts, settings, onAttempt, onEditAttempt, onUndoAttempt, setTab }) {
  const today = designSrsTodayString();
  const topicMap = React.useMemo(() => designSrsTopicById(topics), [topics]);
  const attemptsByTopic = React.useMemo(() => designSrsAttemptsByTopic(attempts), [attempts]);
  const due = React.useMemo(() => designSrsFocusSort(designSrsDueTopics(topics, attemptsByTopic, today)), [topics, attemptsByTopic, today]);
  const parked = React.useMemo(() => designSrsParkedTopics(topics, attemptsByTopic), [topics, attemptsByTopic]);
  const focusCount = Math.max(1, Number(settings.focusCount || 3));
  const budget = Number(settings.dailyBudgetMinutes || 60);
  const focusMinutes = due.slice(0, focusCount).reduce((sum, topic) => sum + designSrsEstimatedMinutes(topic), 0);
  const startedCount = designSrsActiveTopics(topics).filter(topic => topic.started).length;

  const completedToday = React.useMemo(() => (
    Object.values((attempts || [])
      .filter(attempt => designSrsDatePart(attempt.createdAt) === today && !designSrsIsPendingAttempt(attempt))
      .reduce((map, attempt) => {
        const topic = topicMap[attempt.topicId];
        if (!topic) return map;
        const current = map[topic.id];
        if (!current || String(attempt.createdAt || '') > String(current.latestAttempt.createdAt || '')) {
          map[topic.id] = { topic, latestAttempt: attempt, history: attemptsByTopic[topic.id] || [attempt] };
        }
        return map;
      }, {}))
      .sort((a, b) => String(b.latestAttempt.createdAt || '').localeCompare(String(a.latestAttempt.createdAt || '')))
  ), [attempts, attemptsByTopic, topicMap, today]);

  return (
    <div className="col">
      <div className="card">
        <div className="dsa-srs-summary-grid">
          <Stat label="Due now" value={<CountUp to={due.length} />} sub="all visible below" accent />
          <Stat label="Focus set" value={<CountUp to={Math.min(focusCount, due.length)} />} sub={`~${focusMinutes || 0} min of ${budget} min`} />
          <Stat label="Started" value={<CountUp to={startedCount} />} sub={`of ${designSrsActiveTopics(topics).length} in SRS`} />
          <Stat label="Parked" value={<CountUp to={parked.length} />} sub="pending, not advancing" />
        </div>
      </div>

      {!!completedToday.length && (
        <div className="card" style={{borderColor:'var(--accent)'}}>
          <div className="card-h">
            <div>
              <div className="kicker">Completed today</div>
              <div className="card-title">Next occurrences are locked in.</div>
            </div>
            <DesignSrsPill tone="accent">{completedToday.length} done</DesignSrsPill>
          </div>
          <div className="dsa-srs-mini-list">
            {completedToday.map(({ latestAttempt, topic, history }) => (
              <div key={topic.id} className="dsa-srs-history-row">
                <div style={{minWidth:0}}>
                  <div className="dsa-srs-bank-title" style={{marginTop:0}}>{topic.title}</div>
                  <div className="mono muted" style={{fontSize:'11px', marginTop:'4px'}}>
                    {designSrsCategoryLabel(topic.category)} - next {designSrsOccurrenceText(latestAttempt.stage)} on {latestAttempt.dueDate || '-'}
                  </div>
                  <DesignSrsAttemptTimeline
                    attempts={history}
                    topic={topic}
                    onEditAttempt={onEditAttempt}
                    onUndoAttempt={onUndoAttempt}
                    undoableAttemptId={latestAttempt.id}
                  />
                </div>
                <DesignSrsPill tone={designSrsRatingTone(latestAttempt.rating)}>{designSrsRatingLabel(latestAttempt.rating)}</DesignSrsPill>
              </div>
            ))}
          </div>
        </div>
      )}

      {!topics.length && (
        <div className="card" style={{textAlign:'center', padding:'46px'}}>
          <div className="card-title">No design topics in D1 yet.</div>
          <p className="muted" style={{margin:'10px auto 18px', maxWidth:'560px'}}>
            The question bank syncs automatically when this view loads. If nothing appears, run the sync manually from Settings.
          </p>
          <button className="btn primary" onClick={() => setTab('settings')}>Open Settings</button>
        </div>
      )}

      {!!topics.length && !due.length && (
        <div className="card" style={{textAlign:'center', padding:'46px'}}>
          <div className="card-title">No design reviews due right now.</div>
          <p className="muted" style={{marginTop:'10px'}}>Upcoming first rebuilds are visible in Backlog and Roadmap.</p>
        </div>
      )}

      {!!parked.length && (
        <div className="card" style={{borderColor:'var(--warn)'}}>
          <div className="card-h">
            <div>
              <div className="kicker">Parked (kept pending)</div>
              <div className="card-title">These stay due without advancing.</div>
            </div>
            <DesignSrsPill tone="warn">{parked.length}</DesignSrsPill>
          </div>
          <div className="col" style={{marginTop:'12px'}}>
            {parked.map(topic => (
              <DesignSrsReviewCard
                key={topic.id}
                topic={topic}
                onAttempt={onAttempt}
                showPendingAction={false}
              />
            ))}
          </div>
        </div>
      )}

      {due.map((topic, index) => (
        <DesignSrsReviewCard
          key={topic.id}
          topic={topic}
          onAttempt={onAttempt}
          onKeptPending={() => {}}
          focus={index < focusCount}
        />
      ))}

      {due.length > focusCount && (
        <div className="card" style={{borderColor:'var(--warn)'}}>
          <div className="card-title">More due than the 1-hour focus set.</div>
          <p className="muted" style={{marginTop:'8px'}}>
            All {due.length} due units stay visible. The first {focusCount} are the focus set for today's {budget} minutes; Keep pending anything you consciously skip.
          </p>
        </div>
      )}
    </div>
  );
}

function DesignSrsBacklogTab({ topics, attempts, onReschedule, onOpen }) {
  const today = designSrsTodayString();
  const notStarted = designSrsSortTopics(designSrsActiveTopics(topics).filter(topic => !topic.started && !topic.attemptCount));
  const overdue = notStarted.filter(topic => (topic.dueDate || topic.firstRebuildDate || '') <= today);
  const upcoming = notStarted.filter(topic => (topic.dueDate || topic.firstRebuildDate || '') > today);

  const upcomingByDate = {};
  upcoming.forEach(topic => {
    const date = topic.dueDate || topic.firstRebuildDate || 'Unscheduled';
    if (!upcomingByDate[date]) upcomingByDate[date] = [];
    upcomingByDate[date].push(topic);
  });

  return (
    <div className="col">
      <div className="card">
        <div className="dsa-srs-summary-grid">
          <Stat label="Waiting first rebuild" value={<CountUp to={notStarted.length} />} sub="not yet SRS units" accent />
          <Stat label="Seed overdue" value={<CountUp to={overdue.length} />} sub="slid forward, still safe" />
          <Stat label="Upcoming seeds" value={<CountUp to={upcoming.length} />} sub="enter on their queue date" />
        </div>
        <p className="muted" style={{marginTop:'12px'}}>
          Backlog topics become SRS units only after their first completed rebuild. Skipped seeds slide without creating fake occurrences - exactly the PLAN 2 rule.
        </p>
      </div>

      {!!overdue.length && (
        <div className="card" style={{borderColor:'var(--warn)'}}>
          <div className="card-h">
            <div>
              <div className="kicker">Seeds waiting (queue date passed)</div>
              <div className="card-title">These appear in Today until you do the first rebuild.</div>
            </div>
            <DesignSrsPill tone="warn">{overdue.length}</DesignSrsPill>
          </div>
          <div className="dsa-srs-mini-list">
            {overdue.map(topic => (
              <DesignSrsTopicRow
                key={topic.id}
                topic={topic}
                onOpen={onOpen}
                today={today}
                rightSlot={<DesignSrsDueDateControl topic={topic} onReschedule={onReschedule} />}
              />
            ))}
          </div>
        </div>
      )}

      {Object.keys(upcomingByDate).sort().map(date => (
        <div key={date} className="card">
          <div className="card-h">
            <div>
              <div className="kicker">First rebuild {date}</div>
              <div className="card-title">{upcomingByDate[date].length} topic{upcomingByDate[date].length === 1 ? '' : 's'}</div>
            </div>
          </div>
          <div className="dsa-srs-mini-list">
            {upcomingByDate[date].map(topic => (
              <DesignSrsTopicRow
                key={topic.id}
                topic={topic}
                onOpen={onOpen}
                today={today}
                rightSlot={<DesignSrsDueDateControl topic={topic} onReschedule={onReschedule} />}
              />
            ))}
          </div>
        </div>
      ))}

      {!notStarted.length && (
        <div className="card" style={{textAlign:'center', padding:'46px'}}>
          <div className="card-title">Backlog is clear.</div>
          <p className="muted" style={{marginTop:'10px'}}>Every synced topic has already had its first rebuild.</p>
        </div>
      )}
    </div>
  );
}

function DesignSrsCalendarTab({ topics, attempts, post }) {
  const today = designSrsTodayString();
  const [month, setMonth] = React.useState(() => today.slice(0, 7));
  const [selectedDate, setSelectedDate] = React.useState(today);
  const [draggingDate, setDraggingDate] = React.useState('');
  const [dropTargetDate, setDropTargetDate] = React.useState('');
  const [movingDay, setMovingDay] = React.useState(false);
  const topicMap = React.useMemo(() => designSrsTopicById(topics), [topics]);
  const attemptsByTopic = React.useMemo(() => designSrsAttemptsByTopic(attempts), [attempts]);

  async function rescheduleTopic(topicId, dueDate) {
    await post('/api/design-srs/topic', { action: 'reschedule_topic', topicId, dueDate });
  }

  async function rescheduleDay(sourceDate, targetDate) {
    if (!sourceDate || !targetDate || sourceDate === targetDate || movingDay) return;
    const count = designSrsActiveTopics(topics).filter(topic => topic.dueDate === sourceDate).length;
    if (!count) return;
    if (!confirm(`Move all ${count} due design topic${count === 1 ? '' : 's'} from ${sourceDate} to ${targetDate}?`)) return;
    setMovingDay(true);
    try {
      await post('/api/design-srs/topic', { action: 'reschedule_day', sourceDate, targetDate });
      setSelectedDate(targetDate);
    } catch (error) {
      alert(error.message || String(error));
    } finally {
      setDraggingDate('');
      setDropTargetDate('');
      setMovingDay(false);
    }
  }

  const eventsByDate = React.useMemo(() => {
    const groups = {};
    function push(date, event) {
      if (!date) return;
      if (!groups[date]) groups[date] = [];
      groups[date].push(event);
    }
    designSrsActiveTopics(topics).forEach(topic => {
      push(topic.dueDate, { kind: 'due', topic, title: topic.title });
    });
    (attempts || []).forEach(attempt => {
      const topic = topicMap[attempt.topicId];
      if (!topic || designSrsIsPendingAttempt(attempt)) return;
      push(designSrsDatePart(attempt.reviewDate || attempt.createdAt), { kind: 'completed', topic, attempt, title: topic.title });
    });
    Object.values(groups).forEach(list => {
      list.sort((a, b) => {
        if (a.kind !== b.kind) return a.kind === 'due' ? -1 : 1;
        return String(a.title || '').localeCompare(String(b.title || ''));
      });
    });
    return groups;
  }, [topics, attempts, topicMap]);

  const monthDates = React.useMemo(() => {
    const [year, rawMonth] = month.split('-').map(Number);
    const first = new Date(year, rawMonth - 1, 1);
    const start = new Date(first);
    const mondayOffset = (start.getDay() + 6) % 7;
    start.setDate(start.getDate() - mondayOffset);
    return Array.from({ length: 42 }, (_, index) => {
      const date = new Date(start);
      date.setDate(start.getDate() + index);
      return fmtDate(date);
    });
  }, [month]);

  function shiftMonth(delta) {
    const [year, rawMonth] = month.split('-').map(Number);
    const date = new Date(year, rawMonth - 1 + delta, 1);
    setMonth(fmtDate(date).slice(0, 7));
  }

  const selectedEvents = eventsByDate[selectedDate] || [];
  const selectedDue = selectedEvents.filter(event => event.kind === 'due').length;

  return (
    <div className="col">
      <div className="card">
        <div className="card-h">
          <div>
            <div className="kicker">Design SRS Calendar</div>
            <div className="card-title">{month}</div>
          </div>
          <div className="dsa-srs-pill-row">
            <button className="btn" onClick={() => shiftMonth(-1)}>Prev</button>
            <button className="btn primary" onClick={() => { setMonth(today.slice(0, 7)); setSelectedDate(today); }}>Today</button>
            <button className="btn" onClick={() => shiftMonth(1)}>Next</button>
          </div>
        </div>
        <div className="dsa-srs-calendar-grid">
          {['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].map(day => (
            <div key={day} className="mono muted dsa-srs-calendar-weekday">{day}</div>
          ))}
          {monthDates.map(date => {
            const events = eventsByDate[date] || [];
            const completed = events.filter(event => event.kind === 'completed').length;
            const due = events.filter(event => event.kind === 'due').length;
            const inMonth = date.slice(0, 7) === month;
            const active = date === selectedDate;
            return (
              <button
                key={date}
                className={`dsa-srs-calendar-cell ${active ? 'active' : ''} ${!inMonth ? 'muted-month' : ''} ${draggingDate === date ? 'dragging' : ''} ${dropTargetDate === date ? 'drop-target' : ''}`}
                draggable={due > 0 && !movingDay}
                onDragStart={event => {
                  if (!due) return;
                  setDraggingDate(date);
                  event.dataTransfer.effectAllowed = 'move';
                  event.dataTransfer.setData('text/design-srs-date', date);
                }}
                onDragEnd={() => { setDraggingDate(''); setDropTargetDate(''); }}
                onDragOver={event => {
                  if (!draggingDate || draggingDate === date) return;
                  event.preventDefault();
                  event.dataTransfer.dropEffect = 'move';
                  setDropTargetDate(date);
                }}
                onDragLeave={event => {
                  if (!event.currentTarget.contains(event.relatedTarget)) setDropTargetDate('');
                }}
                onDrop={event => {
                  event.preventDefault();
                  const sourceDate = event.dataTransfer.getData('text/design-srs-date') || draggingDate;
                  setDropTargetDate('');
                  rescheduleDay(sourceDate, date);
                }}
                onClick={() => setSelectedDate(date)}
              >
                <div className="dsa-srs-calendar-date">
                  <span>{Number(date.slice(-2))}</span>
                  {date === today && <span className="pulse-dot"></span>}
                </div>
                <div className="dsa-srs-calendar-counts">
                  {!!due && <span>{due} due</span>}
                  {!!completed && <span>{completed} done</span>}
                </div>
                {events.slice(0, 2).map((event, index) => (
                  <div key={`${event.kind}_${event.topic.id}_${index}`} className={`dsa-srs-calendar-chip ${event.kind}`}>
                    {event.title}
                  </div>
                ))}
                {events.length > 2 && <div className="mono muted" style={{fontSize:'9px'}}>+{events.length - 2} more</div>}
              </button>
            );
          })}
        </div>
      </div>

      <div className="card">
        <div className="card-h">
          <div>
            <div className="kicker">Selected date</div>
            <div className="card-title">{selectedDate}</div>
          </div>
          <DesignSrsPill>{selectedEvents.length} events</DesignSrsPill>
        </div>
        {!!selectedDue && (
          <DesignSrsMoveDayControl
            sourceDate={selectedDate}
            dueCount={selectedDue}
            onMove={rescheduleDay}
            saving={movingDay}
          />
        )}
        {!selectedEvents.length ? (
          <p className="muted">No design SRS events on this date.</p>
        ) : (
          <div className="dsa-srs-mini-list">
            {selectedEvents.map((event, index) => (
              <div key={`${event.kind}_${event.topic.id}_${index}`} className="dsa-srs-history-row">
                <div style={{minWidth:0}}>
                  <div className="dsa-srs-pill-row">
                    <DesignSrsPill tone={event.kind === 'completed' ? 'accent' : 'warn'}>
                      {event.kind === 'completed' ? `completed - ${designSrsRatingLabel(event.attempt.rating)}` : 'due'}
                    </DesignSrsPill>
                    <DesignSrsPill>{designSrsCategoryLabel(event.topic.category)}</DesignSrsPill>
                    <DesignSrsPill>
                      {event.kind === 'completed'
                        ? `next ${designSrsOccurrenceText(event.attempt.stage)} on ${event.attempt.dueDate || '-'}`
                        : designSrsOccurrenceText(event.topic.stage)}
                    </DesignSrsPill>
                  </div>
                  <div className="dsa-srs-bank-title">{event.title}</div>
                  {event.kind === 'due' && (
                    <DesignSrsDueDateControl topic={event.topic} onReschedule={rescheduleTopic} label="Move current due" />
                  )}
                </div>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

function DesignSrsMoveDayControl({ sourceDate, dueCount, onMove, saving }) {
  const [targetDate, setTargetDate] = React.useState(sourceDate || '');

  React.useEffect(() => {
    setTargetDate(sourceDate || '');
  }, [sourceDate]);

  if (!dueCount) return null;
  return (
    <div className="dsa-srs-move-day-control">
      <div>
        <div className="kicker">Move whole due day</div>
        <div className="muted">Moves all {dueCount} due topic{dueCount === 1 ? '' : 's'}; completed history stays unchanged.</div>
      </div>
      <input
        className="input"
        type="date"
        value={targetDate}
        aria-label="Move all due topics to date"
        onChange={event => setTargetDate(event.target.value)}
      />
      <button
        className="btn primary"
        disabled={saving || !targetDate || targetDate === sourceDate}
        onClick={() => onMove(sourceDate, targetDate)}
      >
        {saving ? 'Moving...' : 'Move all due'}
      </button>
    </div>
  );
}

function DesignSrsRoadmapTab({ topics, attempts, onOpen }) {
  const today = designSrsTodayString();
  const active = designSrsActiveTopics(topics);
  const groups = {};
  active.forEach(topic => {
    const date = topic.firstRebuildDate || 'Unscheduled';
    if (!groups[date]) groups[date] = [];
    groups[date].push(topic);
  });
  const dates = Object.keys(groups).sort();

  return (
    <div className="col">
      {!dates.length && (
        <div className="card" style={{textAlign:'center', padding:'46px'}}>
          <div className="card-title">No roadmap yet.</div>
          <p className="muted" style={{marginTop:'10px'}}>Sync the question bank from Settings to load the first-rebuild queue.</p>
        </div>
      )}
      {dates.map(date => {
        const list = designSrsSortTopics(groups[date]);
        const started = list.filter(topic => topic.started).length;
        const due = list.filter(topic => topic.dueDate && topic.dueDate <= today).length;
        return (
          <div key={date} className="card dsa-srs-roadmap-day">
            <div className="dsa-srs-roadmap-head">
              <div>
                <div className="kicker">
                  {date === 'Unscheduled' ? 'Unscheduled' : `Queue ${date} ${date <= today ? '- active' : '- upcoming'}`}
                </div>
                <div className="card-title">
                  {list.map(topic => designSrsCategoryLabel(topic.category)).filter((v, i, arr) => arr.indexOf(v) === i).join(' + ')}
                </div>
              </div>
              <div className="dsa-srs-pill-row">
                <DesignSrsPill tone={due ? 'warn' : ''}>{due} due</DesignSrsPill>
                <DesignSrsPill>{started}/{list.length} started</DesignSrsPill>
              </div>
            </div>
            <Bar value={started} total={Math.max(1, list.length)} />
            <div className="dsa-srs-mini-list">
              {list.map(topic => (
                <DesignSrsTopicRow
                  key={topic.id}
                  topic={topic}
                  onOpen={onOpen}
                  today={today}
                  rightSlot={<span className="mono muted">{topic.attemptCount || 0}x</span>}
                />
              ))}
            </div>
          </div>
        );
      })}
    </div>
  );
}

function DesignSrsLibraryTab({ topics, attempts, onOpen }) {
  const today = designSrsTodayString();
  const [query, setQuery] = React.useState('');
  const [categoryFilter, setCategoryFilter] = React.useState('all');
  const [statusFilter, setStatusFilter] = React.useState('all');

  const categories = React.useMemo(() => {
    const seen = new Map();
    (topics || []).forEach(topic => {
      const key = topic.category || 'uncategorized';
      seen.set(key, (seen.get(key) || 0) + 1);
    });
    return [...seen.entries()].sort((a, b) => designSrsCategoryLabel(a[0]).localeCompare(designSrsCategoryLabel(b[0])));
  }, [topics]);

  const filtered = (topics || []).filter(topic => {
    const haystack = `${topic.title} ${topic.category} ${topic.interviewWeight} ${(topic.questions || []).map(q => q.prompt).join(' ')}`.toLowerCase();
    if (query && !haystack.includes(query.toLowerCase())) return false;
    if (categoryFilter !== 'all' && (topic.category || 'uncategorized') !== categoryFilter) return false;
    if (statusFilter === 'due') return topic.status !== 'archived' && topic.dueDate && topic.dueDate <= today;
    if (statusFilter === 'upcoming') return topic.status !== 'archived' && topic.dueDate && topic.dueDate > today;
    if (statusFilter === 'started') return topic.started;
    if (statusFilter === 'not_started') return !topic.started && topic.status !== 'archived';
    if (statusFilter === 'weak') return topic.weak;
    if (statusFilter === 'archived') return topic.status === 'archived';
    return topic.status !== 'archived' || statusFilter === 'all';
  });

  const groups = {};
  designSrsSortTopics(filtered).forEach(topic => {
    const key = topic.category || 'uncategorized';
    if (!groups[key]) groups[key] = [];
    groups[key].push(topic);
  });
  const questionTotal = filtered.reduce((sum, topic) => sum + (topic.questions || []).length, 0);

  return (
    <div className="col">
      <div className="card">
        <div className="dsa-srs-bank-toolbar">
          <input className="input" value={query} onChange={e => setQuery(e.target.value)} placeholder="Search topic, category, question text..." />
          <select className="select" value={categoryFilter} onChange={e => setCategoryFilter(e.target.value)}>
            <option value="all">All categories</option>
            {categories.map(([key, count]) => (
              <option key={key} value={key}>{designSrsCategoryLabel(key)} ({count})</option>
            ))}
          </select>
          <select className="select" value={statusFilter} onChange={e => setStatusFilter(e.target.value)}>
            <option value="all">All topics</option>
            <option value="due">Due now</option>
            <option value="upcoming">Upcoming</option>
            <option value="started">Started</option>
            <option value="not_started">Not started</option>
            <option value="weak">Tagged weak</option>
            <option value="archived">Archived</option>
          </select>
        </div>
        <div className="kicker" style={{marginTop:'16px'}}>{filtered.length} topics · {questionTotal} prompts · all served from D1</div>
        <div className="dsa-srs-topic-groups">
          {Object.keys(groups).sort((a, b) => designSrsCategoryLabel(a).localeCompare(designSrsCategoryLabel(b))).map(key => {
            const list = groups[key];
            const due = list.filter(topic => topic.status !== 'archived' && topic.dueDate && topic.dueDate <= today).length;
            const started = list.filter(topic => topic.started).length;
            return (
              <section key={key} className="dsa-srs-topic-group">
                <div className="dsa-srs-topic-group-head">
                  <div>
                    <div className="kicker">Category</div>
                    <div className="card-title">{designSrsCategoryLabel(key)}</div>
                  </div>
                  <div className="dsa-srs-pill-row">
                    <DesignSrsPill tone={due ? 'warn' : ''}>{due} due</DesignSrsPill>
                    <DesignSrsPill>{started}/{list.length} started</DesignSrsPill>
                  </div>
                </div>
                <div className="dsa-srs-bank-list">
                  {list.map(topic => (
                    <DesignSrsTopicRow
                      key={topic.id}
                      topic={topic}
                      onOpen={onOpen}
                      today={today}
                      rightSlot={<span className="mono muted">{(topic.questions || []).length} prompts</span>}
                    />
                  ))}
                </div>
              </section>
            );
          })}
          {!filtered.length && (
            <div className="dsa-srs-note-box muted">No topics match this search and filter.</div>
          )}
        </div>
      </div>
    </div>
  );
}

function DesignSrsMocksTab({ topics, attempts, onOpen, onAttempt }) {
  const today = designSrsTodayString();
  const attemptsByTopic = React.useMemo(() => designSrsAttemptsByTopic(attempts), [attempts]);
  const mocks = designSrsSortTopics((topics || []).filter(topic => topic.category === 'hld_mock' && topic.status !== 'archived'));

  return (
    <div className="col">
      <div className="card">
        <div className="card-h">
          <div>
            <div className="kicker">HLD Mock rehearsals</div>
            <div className="card-title">Four recorded 45-minute mocks with self-scoring.</div>
          </div>
          <DesignSrsPill tone="accent">{mocks.filter(topic => topic.started).length}/{mocks.length} started</DesignSrsPill>
        </div>
        <p className="muted" style={{marginTop:'10px'}}>
          Each mock is a full talk-through: record it, answer the push-back prompts, then score the recording and log the gaps here so they enter the spaced queue.
        </p>
      </div>

      {!mocks.length && (
        <div className="card" style={{textAlign:'center', padding:'46px'}}>
          <div className="card-title">No mock topics synced yet.</div>
          <p className="muted" style={{marginTop:'10px'}}>Sync the question bank from Settings; the four HLD mocks arrive with batch 06.</p>
        </div>
      )}

      {mocks.map(topic => {
        const history = attemptsByTopic[topic.id] || [];
        const dueNow = topic.dueDate && topic.dueDate <= today && !designSrsIsPendingAttempt(history[0]);
        return dueNow ? (
          <DesignSrsReviewCard key={topic.id} topic={topic} onAttempt={onAttempt} />
        ) : (
          <div key={topic.id} className="card">
            <div className="card-h">
              <div style={{minWidth:0}}>
                <div className="dsa-srs-pill-row">
                  <DesignSrsPill>{topic.interviewWeight || 'core'}</DesignSrsPill>
                  <DesignSrsPill>due {topic.dueDate || '-'}</DesignSrsPill>
                  <DesignSrsPill>{designSrsOccurrenceText(topic.stage)}</DesignSrsPill>
                </div>
                <div className="card-title" style={{marginTop:'8px'}}>{topic.title}</div>
              </div>
              <button className="btn" onClick={() => onOpen(topic)}>Open template</button>
            </div>
            {!!history.length && (
              <DesignSrsAttemptTimeline attempts={history} topic={topic} />
            )}
          </div>
        );
      })}
    </div>
  );
}

function DesignSrsSettingsTab({ settings, bankSync, topics, post, syncBank, syncing, onFlags }) {
  const [draft, setDraft] = React.useState(() => ({ ...DESIGN_SRS_DEFAULT_SETTINGS, ...settings }));
  const [saving, setSaving] = React.useState(false);
  const archived = (topics || []).filter(topic => topic.status === 'archived');

  React.useEffect(() => {
    setDraft({ ...DESIGN_SRS_DEFAULT_SETTINGS, ...settings });
  }, [settings]);

  async function saveSettings(e) {
    e.preventDefault();
    setSaving(true);
    try {
      await post('/api/design-srs/topic', { action: 'settings', settings: draft });
    } catch (error) {
      alert(error.message || String(error));
    } finally {
      setSaving(false);
    }
  }

  return (
    <div className="col">
      <form className="card dsa-srs-settings-grid" onSubmit={saveSettings}>
        <div>
          <div className="kicker">Daily budget (min)</div>
          <input
            className="input"
            type="number"
            min="20"
            max="240"
            value={draft.dailyBudgetMinutes}
            onChange={e => setDraft(d => ({ ...d, dailyBudgetMinutes: e.target.value }))}
          />
        </div>
        <div>
          <div className="kicker">Focus set size</div>
          <input
            className="input"
            type="number"
            min="1"
            max="10"
            value={draft.focusCount}
            onChange={e => setDraft(d => ({ ...d, focusCount: e.target.value }))}
          />
        </div>
        <div>
          <div className="kicker">Interval ladder</div>
          <input className="input" value={DESIGN_SRS_INTERVALS.map(d => `D${d}`).join(' ')} readOnly />
        </div>
        <div className="dsa-srs-settings-copy">
          <div className="card-title">One hour a day, output over rereading.</div>
          <p className="muted" style={{marginTop:'8px'}}>
            Due reviews and the current scheduled topic win first; backlog seeds slide. The ladder itself is fixed to the PLAN 2 evidence-based intervals.
          </p>
        </div>
        <button className="btn primary" disabled={saving}>{saving ? 'Saving...' : 'Save settings'}</button>
      </form>

      <div className="card">
        <div className="card-h">
          <div>
            <div className="kicker">Question bank sync</div>
            <div className="card-title">Repo bank file → D1 topics</div>
          </div>
          <button className="btn primary" disabled={syncing} onClick={() => syncBank({ force: true })}>
            {syncing ? 'Syncing...' : 'Re-sync bank now'}
          </button>
        </div>
        {bankSync ? (
          <div className="dsa-srs-mini-list" style={{marginTop:'12px'}}>
            <div className="mono muted" style={{fontSize:'11px'}}>
              Last synced {bankSync.syncedAt ? bankSync.syncedAt.replace('T', ' ').slice(0, 16) : '-'} · {bankSync.topicCount || 0} topics ·
              bank v{bankSync.version || '?'} · {bankSync.inserted || 0} inserted / {bankSync.updated || 0} updated / {bankSync.archived || 0} archived
            </div>
            <div className="mono muted" style={{fontSize:'11px'}}>fingerprint {bankSync.fingerprint || '-'}</div>
            {!!(bankSync.warnings || []).length && (
              <div className="dsa-srs-note-box">
                <strong>Sync warnings:</strong>
                {(bankSync.warnings || []).map((warning, index) => (
                  <div key={index} className="muted" style={{fontSize:'12px'}}>{warning}</div>
                ))}
              </div>
            )}
          </div>
        ) : (
          <p className="muted" style={{marginTop:'12px'}}>
            Never synced. The view auto-syncs on load; use the button if the bank file changed while this tab was open.
          </p>
        )}
        <p className="muted" style={{marginTop:'12px'}}>
          Sync refreshes question templates from data/design_rebuild_question_bank.json and never touches stages, due dates, or your logged answers. Topics removed from the bank are archived, not deleted.
        </p>
      </div>

      {!!archived.length && (
        <div className="card" style={{borderColor:'var(--warn)'}}>
          <div className="card-h">
            <div>
              <div className="kicker">Archived topics</div>
              <div className="card-title">Removed from the bank, history preserved.</div>
            </div>
            <DesignSrsPill tone="warn">{archived.length}</DesignSrsPill>
          </div>
          <div className="dsa-srs-mini-list">
            {archived.map(topic => (
              <div key={topic.id} className="dsa-srs-history-row">
                <div style={{minWidth:0}}>
                  <div className="dsa-srs-bank-title" style={{marginTop:0}}>{topic.title}</div>
                  <div className="mono muted" style={{fontSize:'11px'}}>{topic.attemptCount || 0} logged reviews retained</div>
                </div>
                <button className="btn" onClick={() => onFlags(topic.id, { status: 'active' })}>Restore</button>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

function DesignRebuildView() {
  const [tab, setTab] = React.useState('today');
  const [selectedTopicId, setSelectedTopicId] = React.useState('');
  const { snapshot, load, post, syncBank, syncing, bankNotice } = useDesignSrsData();
  const { settings, bankSync, topics, attempts, loading, error } = snapshot;

  const today = designSrsTodayString();
  const attemptsByTopic = React.useMemo(() => designSrsAttemptsByTopic(attempts), [attempts]);
  const due = React.useMemo(() => designSrsDueTopics(topics, attemptsByTopic, today), [topics, attemptsByTopic, today]);
  const backlogCount = React.useMemo(() => (
    designSrsActiveTopics(topics).filter(topic => !topic.started && !topic.attemptCount).length
  ), [topics]);
  const startedCount = designSrsActiveTopics(topics).filter(topic => topic.started).length;
  const selectedTopic = React.useMemo(
    () => (topics || []).find(topic => topic.id === selectedTopicId) || null,
    [topics, selectedTopicId]
  );

  async function saveAttempt(topicId, attempt) {
    await post('/api/design-srs/attempt', { topicId, attempt });
  }

  async function saveAttemptEdit(attemptId, patch) {
    await post('/api/design-srs/attempt', { action: 'update', attemptId, patch });
  }

  async function undoAttempt(attemptId) {
    await post('/api/design-srs/attempt', { action: 'undo', attemptId });
  }

  async function rescheduleTopic(topicId, dueDate) {
    await post('/api/design-srs/topic', { action: 'reschedule_topic', topicId, dueDate });
  }

  async function setFlags(topicId, flags) {
    try {
      await post('/api/design-srs/topic', { action: 'flags', topicId, flags });
    } catch (error) {
      alert(error.message || String(error));
    }
  }

  return (
    <div className="fade-in">
      <div className="section-h dsa-srs-hero">
        <div>
          <div className="kicker">Design Rebuild - LLD / HLD / Go concurrency SRS</div>
          <h1 className="h-title">Rebuild, <em>defend</em>, retain.</h1>
        </div>
        <div className="dsa-srs-hero-stats">
          <Stat label="Due" value={<CountUp to={due.length} />} sub={`${settings.focusCount || 3} in focus set`} accent />
          <Stat label="Started" value={<CountUp to={startedCount} />} sub={`of ${designSrsActiveTopics(topics).length} topics`} />
        </div>
      </div>

      <DesignSrsTabs tab={tab} setTab={setTab} dueCount={due.length} backlogCount={backlogCount} />

      {error && (
        <div className="card" style={{borderColor:'var(--rose)', color:'var(--rose)', marginBottom:'18px'}}>
          <div className="kicker" style={{color:'var(--rose)'}}>Design SRS error</div>
          {error}
          <div style={{marginTop:'10px'}}>
            <button className="btn" onClick={() => load()}>Retry</button>
          </div>
        </div>
      )}

      {bankNotice && (
        <div className="card" style={{borderColor:'var(--warn)', marginBottom:'18px'}}>
          <div className="kicker">Bank sync notice</div>
          <p className="muted" style={{marginTop:'6px'}}>{bankNotice}</p>
          <div style={{marginTop:'10px'}}>
            <button className="btn" disabled={syncing} onClick={() => syncBank({ force: true })}>
              {syncing ? 'Syncing...' : 'Retry sync'}
            </button>
          </div>
        </div>
      )}

      {loading ? (
        <div className="card" style={{textAlign:'center', padding:'46px'}}>
          <div className="card-title">Loading design SRS from D1...</div>
        </div>
      ) : (
        <>
          {tab === 'today' && (
            <DesignSrsTodayTab
              topics={topics}
              attempts={attempts}
              settings={settings}
              onAttempt={saveAttempt}
              onEditAttempt={saveAttemptEdit}
              onUndoAttempt={undoAttempt}
              setTab={setTab}
            />
          )}
          {tab === 'backlog' && (
            <DesignSrsBacklogTab
              topics={topics}
              attempts={attempts}
              onReschedule={rescheduleTopic}
              onOpen={topic => setSelectedTopicId(topic.id)}
            />
          )}
          {tab === 'calendar' && <DesignSrsCalendarTab topics={topics} attempts={attempts} post={post} />}
          {tab === 'roadmap' && (
            <DesignSrsRoadmapTab topics={topics} attempts={attempts} onOpen={topic => setSelectedTopicId(topic.id)} />
          )}
          {tab === 'library' && (
            <DesignSrsLibraryTab topics={topics} attempts={attempts} onOpen={topic => setSelectedTopicId(topic.id)} />
          )}
          {tab === 'mocks' && (
            <DesignSrsMocksTab
              topics={topics}
              attempts={attempts}
              onOpen={topic => setSelectedTopicId(topic.id)}
              onAttempt={saveAttempt}
            />
          )}
          {tab === 'settings' && (
            <DesignSrsSettingsTab
              settings={settings}
              bankSync={bankSync}
              topics={topics}
              post={post}
              syncBank={syncBank}
              syncing={syncing}
              onFlags={setFlags}
            />
          )}
        </>
      )}

      <DesignSrsTopicDetailModal
        topic={selectedTopic}
        attempts={selectedTopic ? (attemptsByTopic[selectedTopic.id] || []) : []}
        onClose={() => setSelectedTopicId('')}
        onReschedule={rescheduleTopic}
        onEditAttempt={saveAttemptEdit}
        onUndoAttempt={undoAttempt}
        onFlags={setFlags}
      />
    </div>
  );
}

function DesignSrsTodayPanel({ onOpen }) {
  const [snapshot, setSnapshot] = React.useState({ topics: [], attempts: [], settings: DESIGN_SRS_DEFAULT_SETTINGS, loading: true, error: '' });

  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const response = await fetch('/api/design-srs/state', { credentials: 'include' });
        const data = await response.json();
        if (cancelled) return;
        if (!response.ok || data.error) throw new Error(data.error || 'Could not load Design SRS.');
        setSnapshot({
          settings: { ...DESIGN_SRS_DEFAULT_SETTINGS, ...(data.settings || {}) },
          topics: Array.isArray(data.topics) ? data.topics : [],
          attempts: Array.isArray(data.attempts) ? data.attempts : [],
          loading: false,
          error: '',
        });
      } catch (error) {
        if (!cancelled) setSnapshot(prev => ({ ...prev, loading: false, error: error.message || String(error) }));
      }
    })();
    return () => { cancelled = true; };
  }, []);

  if (snapshot.loading || snapshot.error || !snapshot.topics.length) return null;
  const today = designSrsTodayString();
  const attemptsByTopic = designSrsAttemptsByTopic(snapshot.attempts);
  const due = designSrsFocusSort(designSrsDueTopics(snapshot.topics, attemptsByTopic, today));
  if (!due.length) return null;
  const focusCount = Math.max(1, Number(snapshot.settings.focusCount || 3));

  return (
    <div className="card" style={{marginBottom:'18px', borderColor:'var(--accent)'}}>
      <div className="card-h">
        <div>
          <div className="kicker">Design Rebuild SRS</div>
          <div className="card-title">{due.length} design reviews due</div>
        </div>
        <button className="btn primary" onClick={onOpen}>Start reviews</button>
      </div>
      <div className="dsa-srs-today-mini">
        {due.slice(0, focusCount).map(topic => (
          <div key={topic.id} className="dsa-srs-mini-row">
            <span className="mono muted">{designSrsCategoryLabel(topic.category)}</span>
            <span>{topic.title}</span>
            <span className="mono muted">{designSrsOccurrenceText(topic.stage)}</span>
          </div>
        ))}
        {due.length > focusCount && <div className="mono muted">+ {due.length - focusCount} more due beyond the focus set</div>}
      </div>
    </div>
  );
}

window.DesignRebuildView = DesignRebuildView;
window.DesignSrsTodayPanel = DesignSrsTodayPanel;
