<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>S-Bus PH-3 Annotation Tool</title>
<style>
  * { box-sizing: border-box; }
  body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    max-width: 900px;
    margin: 0 auto;
    padding: 20px;
    background: #fafafa;
    color: #222;
    line-height: 1.5;
  }
  h1 { font-size: 20px; margin: 0 0 10px 0; color: #333; }
  .progress {
    background: #e0e0e0;
    height: 8px;
    border-radius: 4px;
    margin-bottom: 20px;
    overflow: hidden;
  }
  .progress-bar {
    background: linear-gradient(90deg, #4caf50, #66bb6a);
    height: 100%;
    transition: width 0.3s;
  }
  .meta {
    font-size: 13px;
    color: #666;
    margin-bottom: 10px;
  }
  .card {
    background: white;
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 20px;
    margin-bottom: 15px;
    box-shadow: 0 1px 3px rgba(0,0,0,0.05);
  }
  .label {
    font-weight: 600;
    color: #555;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    margin-bottom: 6px;
  }
  .candidate-shard {
    font-family: 'SF Mono', Monaco, Consolas, monospace;
    background: #fff3cd;
    padding: 4px 10px;
    border-radius: 4px;
    border: 1px solid #ffc107;
    font-weight: 600;
    display: inline-block;
  }
  .agent-claim {
    font-size: 13px;
    padding: 6px 10px;
    border-radius: 4px;
    display: inline-block;
    margin-top: 6px;
  }
  .agent-claim.claimed {
    background: #d1e7dd;
    color: #0f5132;
    border: 1px solid #badbcc;
  }
  .agent-claim.notclaimed {
    background: #f8d7da;
    color: #842029;
    border: 1px solid #f5c2c7;
  }
  .change-text {
    background: #f8f9fa;
    border-left: 3px solid #0d6efd;
    padding: 10px 14px;
    font-style: italic;
    color: #333;
    margin-top: 6px;
  }
  pre {
    background: #f8f9fa;
    padding: 12px;
    border-radius: 4px;
    overflow-x: auto;
    font-size: 12px;
    max-height: 300px;
    border: 1px solid #e0e0e0;
    white-space: pre-wrap;
    word-break: break-word;
    color: #333;
  }
  .buttons {
    display: flex;
    gap: 10px;
    justify-content: center;
    margin: 20px 0;
  }
  button {
    padding: 14px 22px;
    font-size: 14px;
    border-radius: 6px;
    border: 2px solid transparent;
    cursor: pointer;
    font-weight: 600;
    transition: transform 0.1s, box-shadow 0.1s;
    min-width: 140px;
  }
  button:hover { transform: translateY(-1px); box-shadow: 0 2px 6px rgba(0,0,0,0.15); }
  button:active { transform: translateY(0); }
  .btn-yes {
    background: #198754; color: white; border-color: #157347;
  }
  .btn-no {
    background: #dc3545; color: white; border-color: #bb2d3b;
  }
  .btn-unclear {
    background: #6c757d; color: white; border-color: #5c636a;
  }
  .btn-nav {
    background: #f0f0f0; color: #333;
  }
  .key-hint {
    font-size: 11px;
    opacity: 0.85;
    font-weight: normal;
    display: block;
    margin-top: 2px;
  }
  .footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 15px;
    font-size: 13px;
    color: #666;
  }
  .export-btn {
    background: #0d6efd; color: white;
    padding: 10px 20px;
    font-weight: 600;
    border: none;
    border-radius: 4px;
  }
  .instructions {
    background: #fff9e6;
    border: 1px solid #ffe08a;
    border-radius: 6px;
    padding: 15px;
    margin-bottom: 20px;
    font-size: 13px;
  }
  .instructions strong { color: #856404; }
  #loader { padding: 30px; text-align: center; color: #666; }
  #loader input { margin: 10px; }
  .done-screen {
    text-align: center;
    padding: 40px;
    background: #d1e7dd;
    border-radius: 8px;
  }
</style>
</head>
<body>

<h1>S-Bus PH-3 Human Annotation — Annotator 1</h1>
<div class="progress"><div class="progress-bar" id="progress-bar" style="width:0%"></div></div>
<div class="meta" id="status-line">Loading…</div>

<div id="loader">
  <p><strong>Step 1:</strong> Load your tasks.json file to begin.</p>
  <input type="file" id="file-input" accept=".json">
</div>

<div id="app" style="display:none;">

<div class="instructions">
  <strong>How to annotate:</strong> You see (a) a text description of what the agent <em>did</em> in this step,
  (b) the change content produced by the agent, and (c) a candidate shard name.
  Your job is to decide: <strong>did the agent actually draw on information from that shard to produce this change?</strong>
  <br><br>
  <strong>Yes</strong> = the change logically depends on the content of that shard. You can see evidence the agent consulted or referenced that shard's data/content/state.
  <br>
  <strong>No</strong> = the change does not depend on that shard. The shard name is in the task's vocabulary but the agent was operating on a different concern.
  <br>
  <strong>Unclear</strong> = you genuinely can't tell with the information shown. Use this sparingly — if forced, lean No rather than Yes.
  <br><br>
  <strong>Keyboard shortcuts:</strong> <kbd>Y</kbd> = Yes, <kbd>N</kbd> = No, <kbd>U</kbd> = Unclear, <kbd>←</kbd>/<kbd>→</kbd> = prev/next row.
</div>

<div class="card">
  <div class="meta" id="task-meta"></div>
  <div style="margin-bottom:18px;">
    <div class="label">Candidate shard (decide about THIS one)</div>
    <span class="candidate-shard" id="candidate-shard">—</span>
    <div class="agent-claim" id="agent-claim"></div>
  </div>
  <div style="margin-bottom:18px;">
    <div class="label">What the agent did (change summary)</div>
    <div class="change-text" id="change-text">—</div>
  </div>
  <div>
    <div class="label">Fresh content block (full context)</div>
    <pre id="fresh-content">—</pre>
  </div>
</div>

<div class="buttons">
  <button class="btn-yes" id="btn-yes">YES – used it<span class="key-hint">(Y)</span></button>
  <button class="btn-no" id="btn-no">NO – did not use it<span class="key-hint">(N)</span></button>
  <button class="btn-unclear" id="btn-unclear">UNCLEAR<span class="key-hint">(U)</span></button>
</div>

<div class="footer">
  <div>
    <button class="btn-nav" id="btn-prev">← Previous</button>
    <button class="btn-nav" id="btn-next">Next →</button>
  </div>
  <div>
    <button class="export-btn" id="btn-export">Export annotations (CSV)</button>
  </div>
</div>
<p style="font-size:12px;color:#888;text-align:center;margin-top:15px;">
  Progress is saved automatically in your browser. You can close this tab and resume later.
</p>

</div>

<script>
let tasks = [];
let labels = {};   // key = taskIdx, value = "yes"|"no"|"unclear"
let cursor = 0;
const STORAGE_KEY = 'sbus_ph3_annotations_v1';

// ── persistence ───────────────────────────────────────────────────────────
function saveState() {
  localStorage.setItem(STORAGE_KEY, JSON.stringify({ labels, cursor }));
}
function loadState() {
  const raw = localStorage.getItem(STORAGE_KEY);
  if (raw) {
    const s = JSON.parse(raw);
    labels = s.labels || {};
    cursor = s.cursor || 0;
  }
}

// ── render ────────────────────────────────────────────────────────────────
function render() {
  if (cursor < 0) cursor = 0;
  if (cursor >= tasks.length) cursor = tasks.length - 1;

  const t = tasks[cursor];
  document.getElementById('task-meta').textContent =
    `Task ${cursor + 1} of ${tasks.length} · domain: ${t.domain} · step: ${t.step} · run: ${t.run_id}`;
  document.getElementById('candidate-shard').textContent = t.candidate_shard;

  const claim = document.getElementById('agent-claim');
  if (t.agent_said_used_it) {
    claim.textContent = 'Agent self-reported: YES, used it';
    claim.className = 'agent-claim claimed';
  } else {
    claim.textContent = 'Agent self-reported: NO, did not use it';
    claim.className = 'agent-claim notclaimed';
  }

  document.getElementById('change-text').textContent = t.change || '(empty)';
  document.getElementById('fresh-content').textContent = t.fresh_content_block || '(empty)';

  // Progress bar
  const done = Object.keys(labels).length;
  document.getElementById('progress-bar').style.width = (done / tasks.length * 100) + '%';
  document.getElementById('status-line').textContent =
    `${done} / ${tasks.length} annotated · ${tasks.length - done} remaining`;

  // Highlight current answer if any
  ['btn-yes', 'btn-no', 'btn-unclear'].forEach(id => {
    document.getElementById(id).style.outline = 'none';
  });
  const current = labels[cursor];
  if (current === 'yes') document.getElementById('btn-yes').style.outline = '4px solid #0f5132';
  if (current === 'no')  document.getElementById('btn-no').style.outline  = '4px solid #842029';
  if (current === 'unclear') document.getElementById('btn-unclear').style.outline = '4px solid #383d41';

  saveState();
}

// ── actions ───────────────────────────────────────────────────────────────
function setLabel(v) {
  labels[cursor] = v;
  saveState();
  // Auto-advance to next unlabeled task
  const next = findNextUnlabeled(cursor);
  if (next !== -1) cursor = next;
  render();
}
function findNextUnlabeled(from) {
  for (let i = from + 1; i < tasks.length; i++) {
    if (!(i in labels)) return i;
  }
  // None ahead — wrap to look back
  for (let i = 0; i < from; i++) {
    if (!(i in labels)) return i;
  }
  return -1;  // all done
}

// ── CSV export ────────────────────────────────────────────────────────────
function exportCsv() {
  const rows = [['row_idx','run_id','domain','task_id','step','candidate_shard','agent_said_used_it','human_label']];
  tasks.forEach((t, i) => {
    const lab = labels[i] || '';
    rows.push([
      t.row_idx, t.run_id, t.domain, t.task_id, t.step,
      t.candidate_shard,
      t.agent_said_used_it ? 'yes' : 'no',
      lab
    ]);
  });
  const csv = rows.map(r => r.map(c => {
    const s = String(c);
    return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
  }).join(',')).join('\n');
  const blob = new Blob([csv], {type: 'text/csv'});
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'annotator1_labels.csv';
  a.click();
  URL.revokeObjectURL(url);
}

// ── event handlers ────────────────────────────────────────────────────────
document.getElementById('file-input').addEventListener('change', e => {
  const file = e.target.files[0];
  if (!file) return;
  const reader = new FileReader();
  reader.onload = ev => {
    tasks = JSON.parse(ev.target.result);
    loadState();
    document.getElementById('loader').style.display = 'none';
    document.getElementById('app').style.display = 'block';
    render();
  };
  reader.readAsText(file);
});

document.getElementById('btn-yes').onclick     = () => setLabel('yes');
document.getElementById('btn-no').onclick      = () => setLabel('no');
document.getElementById('btn-unclear').onclick = () => setLabel('unclear');
document.getElementById('btn-prev').onclick    = () => { cursor--; render(); };
document.getElementById('btn-next').onclick    = () => { cursor++; render(); };
document.getElementById('btn-export').onclick  = exportCsv;

document.addEventListener('keydown', e => {
  if (tasks.length === 0) return;
  if (e.target.tagName === 'INPUT') return;
  const key = e.key.toLowerCase();
  if (key === 'y') setLabel('yes');
  else if (key === 'n') setLabel('no');
  else if (key === 'u') setLabel('unclear');
  else if (e.key === 'ArrowLeft')  { cursor--; render(); }
  else if (e.key === 'ArrowRight') { cursor++; render(); }
});
</script>
</body>
</html>