{% extends "base.html" %}
{% block title %}ABREKA — Dashboard{% endblock %}

{% block content %}
<h1 style="margin-bottom: 4px;">{{ config.project.name }}</h1>
<p class="text-muted mb-2">{{ config.project.lib }} — Autonomous Research Agent</p>

{% if stop_requested %}
<div id="graceful-stop-banner" class="card mb-2" style="border-left: 4px solid #eab308;">
  <strong>Graceful stop queued.</strong>
  ABREKA will stop after the current experiment completes.
</div>
{% endif %}

{% if goal %}
<div class="card editable-card" data-endpoint="/api/goal">
  <div class="file-path">abreka/goal.md</div>
  <h3>Research Goal</h3>
  <div class="view-mode">
    <div class="md-content">{{ goal | md }}</div>
  </div>
  <div class="edit-mode">
    <textarea class="edit-area">{{ goal }}</textarea>
    <div class="edit-actions">
      <button class="btn btn-primary btn-save">Save</button>
      <button class="btn btn-discard">Discard</button>
    </div>
  </div>
  <script type="text/plain" class="raw-content">{{ goal }}</script>
</div>
{% endif %}

<div class="grid grid-4 mb-2">
  <div class="card stat">
    <div class="value">{{ total }}</div>
    <div class="label">Total Experiments</div>
  </div>
  <div class="card stat">
    <div class="value" style="color: #22c55e;">{{ status_counts.get('completed', 0) }}</div>
    <div class="label">Completed</div>
  </div>
  <div class="card stat">
    <div class="value" style="color: #eab308;">{{ status_counts.get('running', 0) + status_counts.get('implementing', 0) + status_counts.get('testing', 0) }}</div>
    <div class="label">In Progress</div>
  </div>
  <div class="card stat">
    <a href="/backlog?status=open" style="text-decoration: none; color: inherit;">
      <div class="value" style="color: #3b82f6;">{{ backlog_open }}</div>
      <div class="label">Backlog Ideas</div>
    </a>
  </div>
</div>

{% if status_counts %}
<div class="card mb-2">
  <h3>Status Breakdown</h3>
  <div class="flex flex-wrap gap-1 mt-1">
    {% for status, count in status_counts.items() %}
    <a href="/experiments?status={{ status }}" style="text-decoration: none;">
      <span class="badge" style="background: {{ status | status_color }};">{{ status }} ({{ count }})</span>
    </a>
    {% endfor %}
  </div>
</div>
{% endif %}

{% if timeline.points %}
<div class="card mb-2">
  <div class="flex items-center" style="justify-content: space-between; gap: 12px; flex-wrap: wrap;">
    <h3 style="margin: 0;">Metric Timeline — {{ timeline.primary_metric }} ({{ timeline.direction }})</h3>
    <label class="text-sm text-muted" style="user-select: none; cursor: pointer;">
      <input type="checkbox" id="timeline-fit-all" style="vertical-align: middle;">
      Include outliers in scale
    </label>
  </div>
  <div id="metric-timeline-container" style="position: relative; width: 100%; height: 340px; margin-top: 8px;">
    <svg id="metric-timeline" style="width: 100%; height: 100%; display: block;"></svg>
    <div id="timeline-tooltip" style="position: absolute; display: none; pointer-events: none;
      background: rgba(26,26,46,0.95); color: #fff; padding: 6px 10px; border-radius: 6px;
      font-size: 12px; line-height: 1.4; white-space: nowrap; z-index: 10;
      box-shadow: 0 2px 8px rgba(0,0,0,0.15);"></div>
  </div>
  <div id="timeline-caption" class="text-sm text-muted mt-1">
    Dots are colored by branch root (topmost ancestor). Solid segments connect
    consecutive experiments on the same branch; the dotted line tracks the best
    metric so far. Click a dot to open the experiment.
  </div>
  <script type="application/json" id="timeline-data">{{ timeline | tojson }}</script>
  <script>
  (function() {
    const data = JSON.parse(document.getElementById('timeline-data').textContent);
    const points = data.points || [];
    if (!points.length) return;

    const maximize = data.direction !== 'minimize';
    const svg = document.getElementById('metric-timeline');
    const container = document.getElementById('metric-timeline-container');
    const tooltip = document.getElementById('timeline-tooltip');
    const fitAllBox = document.getElementById('timeline-fit-all');
    const caption = document.getElementById('timeline-caption');
    const NS = 'http://www.w3.org/2000/svg';

    function branchColor(rootId) {
      const n = parseInt(rootId, 10) || 0;
      const hue = ((n * 137.508) % 360 + 360) % 360;
      return 'hsl(' + hue.toFixed(1) + ', 65%, 48%)';
    }

    function fmt(v) {
      if (!isFinite(v)) return String(v);
      const abs = Math.abs(v);
      if (abs !== 0 && (abs < 0.01 || abs >= 10000)) return v.toExponential(2);
      return v.toFixed(4).replace(/\.?0+$/, '');
    }

    function mk(tag, attrs, text) {
      const el = document.createElementNS(NS, tag);
      for (const k in attrs) el.setAttribute(k, attrs[k]);
      if (text != null) el.textContent = text;
      return el;
    }

    function quantile(sorted, q) {
      const pos = (sorted.length - 1) * q;
      const lo = Math.floor(pos);
      const hi = Math.ceil(pos);
      if (lo === hi) return sorted[lo];
      return sorted[lo] + (sorted[hi] - sorted[lo]) * (pos - lo);
    }

    // Tukey fences on the combined set of y and best_so_far. Returns the
    // robust [lo, hi] range plus the number of points that fall outside it.
    function robustYRange(values) {
      const n = values.length;
      const sorted = values.slice().sort((a, b) => a - b);
      const dataLo = sorted[0];
      const dataHi = sorted[n - 1];
      if (n < 5) return {lo: dataLo, hi: dataHi, clippedLo: 0, clippedHi: 0};
      const q1 = quantile(sorted, 0.25);
      const q3 = quantile(sorted, 0.75);
      const iqr = q3 - q1;
      if (iqr === 0) return {lo: dataLo, hi: dataHi, clippedLo: 0, clippedHi: 0};
      const fenceLo = q1 - 1.5 * iqr;
      const fenceHi = q3 + 1.5 * iqr;
      let clippedLo = 0, clippedHi = 0;
      for (const v of values) {
        if (v < fenceLo) clippedLo++;
        else if (v > fenceHi) clippedHi++;
      }
      return {
        lo: Math.max(fenceLo, dataLo),
        hi: Math.min(fenceHi, dataHi),
        clippedLo, clippedHi,
      };
    }

    function render() {
      while (svg.firstChild) svg.removeChild(svg.firstChild);
      const rect = svg.getBoundingClientRect();
      const W = Math.max(rect.width, 320);
      const H = Math.max(rect.height, 200);
      svg.setAttribute('viewBox', '0 0 ' + W + ' ' + H);
      const M = {top: 18, right: 20, bottom: 40, left: 64};
      const iw = W - M.left - M.right;
      const ih = H - M.top - M.bottom;

      const fitAll = !!(fitAllBox && fitAllBox.checked);
      const xs = points.map(p => p.x);
      const ys = points.map(p => p.y);
      const bs = points.map(p => p.best_so_far);
      const xMin = Math.min.apply(null, xs);
      const xMax = Math.max.apply(null, xs);
      const yAll = ys.concat(bs);

      let yLo, yHi, clippedLo = 0, clippedHi = 0;
      if (fitAll) {
        yLo = Math.min.apply(null, yAll);
        yHi = Math.max.apply(null, yAll);
      } else {
        const r = robustYRange(yAll);
        yLo = r.lo; yHi = r.hi;
        clippedLo = r.clippedLo; clippedHi = r.clippedHi;
      }

      const xRange = xMax - xMin || 1;
      const yRange = yHi - yLo || Math.max(Math.abs(yHi) * 0.1, 1e-9);
      const xPad = xRange * 0.05;
      const yPad = yRange * 0.1;
      const x0 = xMin - xPad;
      const x1 = xMax + xPad;
      const y0 = yLo - yPad;
      const y1 = yHi + yPad;

      function sx(x) { return M.left + (x - x0) / (x1 - x0) * iw; }
      // No axis inversion: higher y is always drawn higher; for "minimize"
      // this means "better" is visually down, matching the metric's semantics.
      function sy(y) {
        const t = (y - y0) / (y1 - y0);
        return M.top + (1 - t) * ih;
      }
      // Clamp a y to the visible domain so outliers render at the edge.
      function clampY(y) { return Math.min(Math.max(y, y0), y1); }

      // Axes
      svg.appendChild(mk('line', {x1: M.left, y1: M.top + ih, x2: M.left + iw, y2: M.top + ih, stroke: '#d0d4da'}));
      svg.appendChild(mk('line', {x1: M.left, y1: M.top, x2: M.left, y2: M.top + ih, stroke: '#d0d4da'}));

      // X ticks
      const nXTicks = Math.min(6, Math.max(2, Math.round(iw / 100)));
      for (let i = 0; i < nXTicks; i++) {
        const xv = xMin + (xMax - xMin) * i / (nXTicks - 1);
        const tx = sx(xv);
        svg.appendChild(mk('line', {x1: tx, y1: M.top + ih, x2: tx, y2: M.top + ih + 5, stroke: '#d0d4da'}));
        svg.appendChild(mk('text', {
          x: tx, y: M.top + ih + 18, 'text-anchor': 'middle',
          'font-size': 11, fill: '#6b7280',
        }, String(Math.round(xv))));
      }

      // Y ticks
      const nYTicks = 5;
      for (let i = 0; i < nYTicks; i++) {
        const t = i / (nYTicks - 1);
        const yv = y0 + (y1 - y0) * t;
        const ty = sy(yv);
        svg.appendChild(mk('line', {x1: M.left - 5, y1: ty, x2: M.left, y2: ty, stroke: '#d0d4da'}));
        svg.appendChild(mk('line', {
          x1: M.left, y1: ty, x2: M.left + iw, y2: ty,
          stroke: '#eef0f3', 'stroke-dasharray': '2 4',
        }));
        svg.appendChild(mk('text', {
          x: M.left - 8, y: ty + 4, 'text-anchor': 'end',
          'font-size': 11, fill: '#6b7280',
        }, fmt(yv)));
      }

      // Axis labels
      svg.appendChild(mk('text', {
        x: M.left + iw / 2, y: H - 6, 'text-anchor': 'middle',
        'font-size': 12, fill: '#6b7280',
      }, 'Experiment #'));
      const ylx = 16;
      const yly = M.top + ih / 2;
      svg.appendChild(mk('text', {
        x: ylx, y: yly, 'text-anchor': 'middle',
        'font-size': 12, fill: '#6b7280',
        transform: 'rotate(-90 ' + ylx + ' ' + yly + ')',
      }, data.primary_metric + (maximize ? ' (maximize)' : ' (minimize)')));

      // Best-so-far dotted line — clamp to visible domain so it stays on-chart.
      const bestPts = points.map(p => sx(p.x) + ',' + sy(clampY(p.best_so_far))).join(' ');
      svg.appendChild(mk('polyline', {
        points: bestPts, fill: 'none', stroke: '#9ca3af',
        'stroke-width': 1.5, 'stroke-dasharray': '4 4',
      }));

      // Branch segments: only between consecutive points sharing branch_root.
      // Segments involving an outlier are drawn faded.
      for (let i = 1; i < points.length; i++) {
        const a = points[i - 1];
        const b = points[i];
        if (a.branch_root !== b.branch_root) continue;
        const aOut = a.y < y0 || a.y > y1;
        const bOut = b.y < y0 || b.y > y1;
        svg.appendChild(mk('line', {
          x1: sx(a.x), y1: sy(clampY(a.y)),
          x2: sx(b.x), y2: sy(clampY(b.y)),
          stroke: branchColor(a.branch_root),
          'stroke-width': 2,
          'stroke-opacity': (aOut || bOut) ? 0.25 : 0.75,
          'stroke-dasharray': (aOut || bOut) ? '2 3' : null,
        }));
      }

      // Decoration layer for branch hover highlight — sits beneath the dots
      // so the offset black marker reads like a drop-shadow tag.
      const decoGroup = mk('g', {
        id: 'timeline-decos',
        style: 'pointer-events: none;',
      });
      svg.appendChild(decoGroup);

      const OFFSET_X = 4;
      const OFFSET_Y = 4;

      function highlightBranch(rootId) {
        while (decoGroup.firstChild) decoGroup.removeChild(decoGroup.firstChild);
        for (const p of points) {
          if (p.branch_root !== rootId) continue;
          const isBelow = p.y < y0;
          const isAbove = p.y > y1;
          const cx = sx(p.x);
          const cy = sy(clampY(p.y));
          if (isAbove || isBelow) {
            const d = 6;
            const tip = isAbove ? (cy - d) : (cy + d);
            const baseY = isAbove ? (cy + d / 1.5) : (cy - d / 1.5);
            const path = 'M ' + (cx + OFFSET_X) + ',' + (tip + OFFSET_Y) +
                         ' L ' + (cx + OFFSET_X - d) + ',' + (baseY + OFFSET_Y) +
                         ' L ' + (cx + OFFSET_X + d) + ',' + (baseY + OFFSET_Y) + ' Z';
            decoGroup.appendChild(mk('path', {
              d: path, fill: '#111', 'fill-opacity': 0.75,
            }));
          } else {
            decoGroup.appendChild(mk('circle', {
              cx: cx + OFFSET_X, cy: cy + OFFSET_Y, r: 5,
              fill: '#111', 'fill-opacity': 0.75,
            }));
          }
        }
      }

      function clearHighlight() {
        while (decoGroup.firstChild) decoGroup.removeChild(decoGroup.firstChild);
      }

      // Dots — clamped to axis edge with a triangle marker for outliers.
      for (const p of points) {
        const isBelow = p.y < y0;
        const isAbove = p.y > y1;
        const outlier = isBelow || isAbove;
        const cx = sx(p.x);
        const cy = sy(clampY(p.y));
        const color = branchColor(p.branch_root);

        let marker;
        if (outlier) {
          // Triangle pointing toward the off-chart direction.
          const d = 6;
          const tip = isAbove ? (cy - d) : (cy + d);
          const baseY = isAbove ? (cy + d / 1.5) : (cy - d / 1.5);
          const path = 'M ' + cx + ',' + tip +
                       ' L ' + (cx - d) + ',' + baseY +
                       ' L ' + (cx + d) + ',' + baseY + ' Z';
          marker = mk('path', {
            d: path, fill: color, stroke: '#fff', 'stroke-width': 1.5,
            style: 'cursor: pointer;',
          });
        } else {
          marker = mk('circle', {
            cx: cx, cy: cy, r: 5, fill: color,
            stroke: '#fff', 'stroke-width': 1.5,
            style: 'cursor: pointer;',
          });
        }

        marker.addEventListener('click', function() {
          window.location.href = '/experiments/' + p.id;
        });
        marker.addEventListener('mouseenter', function() {
          if (!outlier) marker.setAttribute('r', 7);
          highlightBranch(p.branch_root);
          tooltip.style.display = 'block';
          tooltip.innerHTML =
            '<strong>' + p.id + '</strong>' +
            ' <span style="color:#a5b4c8">(branch ' + p.branch_root + ')</span>' +
            '<br>' + data.primary_metric + ' = <strong>' + fmt(p.y) + '</strong>' +
            (outlier ? ' <span style="color:#fbbf24">(off-chart)</span>' : '') +
            '<br>best so far = ' + fmt(p.best_so_far) +
            (p.hypothesis ? '<br><span style="color:#a5b4c8">' +
              (p.hypothesis.length > 60 ? p.hypothesis.slice(0, 60) + '…' : p.hypothesis) +
              '</span>' : '');
        });
        marker.addEventListener('mousemove', function(e) {
          const cr = container.getBoundingClientRect();
          const tw = tooltip.offsetWidth;
          const th = tooltip.offsetHeight;
          let x = e.clientX - cr.left + 12;
          let y = e.clientY - cr.top + 12;
          if (x + tw > cr.width - 4) x = e.clientX - cr.left - tw - 12;
          if (y + th > cr.height - 4) y = e.clientY - cr.top - th - 12;
          tooltip.style.left = x + 'px';
          tooltip.style.top = y + 'px';
        });
        marker.addEventListener('mouseleave', function() {
          if (!outlier) marker.setAttribute('r', 5);
          clearHighlight();
          tooltip.style.display = 'none';
        });
        svg.appendChild(marker);
      }

      // Update caption with outlier info.
      const clipped = clippedLo + clippedHi;
      if (!fitAll && clipped > 0) {
        caption.innerHTML =
          'Y axis clipped to the IQR-based typical range; ' +
          '<strong>' + clipped + '</strong> outlier value' + (clipped === 1 ? '' : 's') +
          ' drawn as triangle' + (clipped === 1 ? '' : 's') + ' at the axis edge. ' +
          'Toggle "Include outliers in scale" to see the full range.';
      } else {
        caption.textContent =
          'Dots are colored by branch root (topmost ancestor). Solid segments ' +
          'connect consecutive experiments on the same branch; the dotted line ' +
          'tracks the best metric so far. Click a dot to open the experiment.';
      }
    }

    if (fitAllBox) fitAllBox.addEventListener('change', render);
    render();
    let raf = null;
    window.addEventListener('resize', function() {
      if (raf) cancelAnimationFrame(raf);
      raf = requestAnimationFrame(render);
    });
  })();
  </script>
</div>
{% endif %}

<div class="card">
  <h3>Recent Experiments</h3>
  {% if recent %}
  <table>
    <thead>
      <tr>
        <th>ID</th>
        <th>Status</th>
        <th>Hypothesis</th>
        <th>Created</th>
      </tr>
    </thead>
    <tbody>
      {% for exp in recent %}
      <tr>
        <td><a href="/experiments/{{ exp.id }}" class="text-mono">{{ exp.id }}</a></td>
        <td><span class="badge" style="background: {{ exp.status | status_color }};">{{ exp.status }}</span></td>
        <td>{{ exp.hypothesis[:80] }}{% if exp.hypothesis|length > 80 %}...{% endif %}</td>
        <td class="text-muted text-sm">{{ exp.created_at[:16] }}</td>
      </tr>
      {% endfor %}
    </tbody>
  </table>
  {% else %}
  <p class="text-muted">No experiments yet. Run <code>abreka propose</code> to create one.</p>
  {% endif %}
</div>
{% endblock %}
