/**
 * job-reconnect.js — find a background op that is still running, after a reload.
 *
 * The job always survived navigating away; the UI simply had no way to find it
 * again, so it looked lost. That gap applied to every bg op — ADOP phases,
 * service restarts, adcmctl, clone steps — because they all share one ebs_jobs
 * row, so it is fixed once here rather than in each page's bespoke renderer.
 *
 * Usage: include the script and tell it which connection to watch.
 *     <script src="/job-reconnect.js"></script>
 *     tvJobReconnect.watch(connectionId);
 * Call watch() again when the selected connection changes; watch(null) stops.
 *
 * Deliberately self-contained: it renders its own banner rather than calling
 * into page internals, so adding it to a page cannot break that page's layout
 * or depend on its markup.
 */
(function () {
  'use strict';

  var POLL_MS = 5000;
  var state = { connId: null, jobId: null, timer: null, el: null };

  function css() {
    if (document.getElementById('tv-jobrc-css')) return;
    var s = document.createElement('style');
    s.id = 'tv-jobrc-css';
    s.textContent = [
      '.tv-jobrc{position:fixed;right:16px;bottom:16px;z-index:10000;max-width:460px;',
      'background:#111114;border:1px solid #f0a830;border-radius:10px;padding:12px 14px;',
      'font-family:"Space Grotesk",-apple-system,sans-serif;color:#e8e8ed;',
      'box-shadow:0 8px 28px rgba(0,0,0,.5)}',
      '.tv-jobrc-hd{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:700;color:#f0a830}',
      '.tv-jobrc-meta{font-size:11px;color:#9898a8;margin-top:2px}',
      '.tv-jobrc-log{margin-top:8px;max-height:120px;overflow:auto;background:#0a0a0c;',
      'border-radius:6px;padding:8px;font-family:"JetBrains Mono",monospace;font-size:10.5px;',
      'color:#9898a8;white-space:pre-wrap;word-break:break-all}',
      '.tv-jobrc-x{margin-left:auto;background:transparent;border:0;color:#9898a8;cursor:pointer;font-size:15px}',
      '.tv-jobrc-done{border-color:#34d399}.tv-jobrc-done .tv-jobrc-hd{color:#34d399}',
      '.tv-jobrc-fail{border-color:#f87171}.tv-jobrc-fail .tv-jobrc-hd{color:#f87171}'
    ].join('');
    document.head.appendChild(s);
  }

  function el() {
    if (state.el && document.body.contains(state.el)) return state.el;
    css();
    var d = document.createElement('div');
    d.className = 'tv-jobrc';
    d.innerHTML = '<div class="tv-jobrc-hd"><span class="tv-jobrc-title"></span>' +
                  '<button class="tv-jobrc-x" title="Hide">&times;</button></div>' +
                  '<div class="tv-jobrc-meta"></div><div class="tv-jobrc-log"></div>';
    d.querySelector('.tv-jobrc-x').onclick = function () { hide(); stop(); };
    document.body.appendChild(d);
    state.el = d;
    return d;
  }

  function hide() { if (state.el) state.el.style.display = 'none'; }

  function render(info, finalState) {
    var d = el();
    d.style.display = '';
    d.classList.remove('tv-jobrc-done', 'tv-jobrc-fail');
    var mins = Math.floor((info.elapsed_sec || 0) / 60), secs = (info.elapsed_sec || 0) % 60;
    var title, meta;
    if (!finalState) {
      title = '\u23F3 ' + (info.op_key || 'operation') + ' still running';
      meta  = 'job #' + info.job_id + ' \u00B7 ' + mins + 'm ' + secs + 's elapsed' +
              ' \u00B7 reconnected after page load';
    } else if (finalState === 'done') {
      d.classList.add('tv-jobrc-done');
      title = '\u2713 ' + (info.op_key || 'operation') + ' completed';
      meta  = 'job #' + info.job_id + ' finished while you were away';
    } else {
      d.classList.add('tv-jobrc-fail');
      title = '\u2717 ' + (info.op_key || 'operation') + ' ' + finalState;
      // Always say WHY — a bare status is the "Exit code null" problem again.
      meta  = 'job #' + info.job_id + (info.error ? ' \u00B7 ' + info.error : '') +
              (info.exit_code != null ? ' \u00B7 exit code ' + info.exit_code : '');
    }
    d.querySelector('.tv-jobrc-title').textContent = title;
    d.querySelector('.tv-jobrc-meta').textContent = meta;
    var log = d.querySelector('.tv-jobrc-log');
    var tail = info.log_tail || info.stdout || '';
    log.textContent = tail.split('\n').slice(-12).join('\n');
    log.scrollTop = log.scrollHeight;
  }

  function stop() { if (state.timer) { clearInterval(state.timer); state.timer = null; } state.jobId = null; }

  async function pollJob(jobId, opKey) {
    try {
      var r = await fetch('/api/ebs-ops/jobs/' + jobId, { credentials: 'same-origin' });
      var j = await r.json();
      if (!r.ok) return;                       // transient: keep watching
      j.job_id = jobId; j.op_key = opKey;
      if (j.status === 'running' || j.status === 'queued') { render(j); return; }
      render(j, j.status === 'done' && j.ok !== false ? 'done' : j.status);
      stop();
    } catch (e) { /* transient — a polling blip is not the job failing */ }
  }

  async function check() {
    if (!state.connId) return;
    try {
      var r = await fetch('/api/ebs-ops/jobs/active?connection_id=' + state.connId,
                          { credentials: 'same-origin' });
      var j = await r.json();
      if (!r.ok || !j.active) return;
      if (state.jobId === j.job_id) return;    // already watching it
      state.jobId = j.job_id;
      render(j);
      if (state.timer) clearInterval(state.timer);
      state.timer = setInterval(function () { pollJob(j.job_id, j.op_key); }, POLL_MS);
    } catch (e) { /* no banner is better than a wrong one */ }
  }

  window.tvJobReconnect = {
    watch: function (connectionId) {
      stop(); hide();
      state.connId = connectionId ? parseInt(connectionId, 10) : null;
      if (state.connId) check();
    },
    _check: check,
  };
})();
