/* * DigestRender — turns digest JSON into a canvas of DigestWindows. * * Everything in here is written around one requirement from the plan's testing * checklist: "If a run's LLM output produces malformed canvas-SDK calls, does the * digest fall back to plain text instead of a broken/blank page?" A local model * emitting near-miss JSON four times a day is a routine event, not an exception, * so every layer degrades instead of throwing: * * - unparseable input -> a
 dump of the raw text
 *   - a section that isn't a doc -> a 
 dump of that section, others still render
 *   - a window that throws       -> a 
 dump of that window, siblings still render
 *   - a globe with no markers    -> the globe still draws, just empty
 *
 * A blank page is the one outcome that must never happen.
 */

(function (global) {
  'use strict';

  function rawDump(container, value, note) {
    var pre = document.createElement('pre');
    pre.className = 'digest-canvas-raw';
    var text = typeof value === 'string' ? value : safeStringify(value);
    pre.textContent = (note ? note + '\n\n' : '') + text;
    container.appendChild(pre);
    return pre;
  }

  function safeStringify(value) {
    try {
      return JSON.stringify(value, null, 2);
    } catch (err) {
      return String(value);
    }
  }

  function parse(input) {
    if (typeof input !== 'string') { return input; }
    try {
      return JSON.parse(input);
    } catch (err) {
      return { __unparseable: input };
    }
  }

  // Accepts what run.py writes ({sections: {compact: [...], full: [...]}}), a bare
  // array of section documents, or a single section document.
  function toSectionList(parsed, detailLevel) {
    if (Array.isArray(parsed)) { return parsed; }
    if (!parsed || typeof parsed !== 'object') { return []; }
    if (parsed.sections && typeof parsed.sections === 'object') {
      var sections = parsed.sections;
      if (Array.isArray(sections)) { return sections; }
      var picked = sections[detailLevel] || sections.full || sections.compact;
      return Array.isArray(picked) ? picked : [];
    }
    if (parsed.windows) { return [parsed]; }
    return [];
  }

  function renderGlobeWindow(container, win) {
    var mount = document.createElement('div');
    var el = global.DigestWindow.open({
      title: win.title || 'Globe',
      content: mount,
      container: container,
      variant: 'globe'
    });

    var globe = new global.DigestGlobe(mount);
    (win.globe_markers || []).forEach(function (marker) {
      try {
        globe.addMarker(marker.lat, marker.lon, {
          icon: marker.icon,
          color: marker.color,
          glow: marker.glow,
          label: marker.label
        });
      } catch (err) {
        // One bad marker must not cost the whole globe.
        if (global.console) { global.console.warn('digest: skipped a globe marker', err); }
      }
    });

    if (typeof win.content === 'string' && win.content.trim()) {
      var caption = document.createElement('p');
      caption.className = 'digest-globe-caption';
      caption.textContent = win.content;
      mount.parentNode.appendChild(caption);
    }

    return el;
  }

  function renderWindow(container, win) {
    if (!win || typeof win !== 'object') {
      return rawDump(container, win, '// malformed window');
    }
    if (win.kind === 'globe') {
      return renderGlobeWindow(container, win);
    }
    return global.DigestWindow.open({
      title: win.title || '',
      content: win.content,
      container: container,
      x: win.x,
      y: win.y,
      w: win.w,
      h: win.h
    });
  }

  function renderSection(container, doc) {
    if (!doc || typeof doc !== 'object' || !Array.isArray(doc.windows)) {
      return rawDump(container, doc, '// section did not match the digest schema');
    }

    doc.windows.forEach(function (win) {
      try {
        var el = renderWindow(container, win);
        if (el && doc.section) {
          el.dataset.section = doc.section;
        }
      } catch (err) {
        if (global.console) { global.console.warn('digest: window fell back to plain text', err); }
        rawDump(container, win, '// window failed to render: ' + err);
      }
    });
  }

  var DigestRender = {
    render: function (container, input, options) {
      options = options || {};
      container.innerHTML = '';
      container.classList.add('digest-canvas');

      var parsed = parse(input);

      if (parsed && parsed.__unparseable !== undefined) {
        rawDump(container, parsed.__unparseable, '// digest JSON did not parse');
        return;
      }

      var sections = toSectionList(parsed, options.detailLevel || 'full');

      if (!sections.length) {
        rawDump(container, parsed, '// no digest sections found in this document');
        return;
      }

      sections.forEach(function (doc) {
        try {
          renderSection(container, doc);
        } catch (err) {
          if (global.console) { global.console.warn('digest: section fell back to plain text', err); }
          rawDump(container, doc, '// section failed to render: ' + err);
        }
      });
    },

    // Convenience used by both templates: fetch, render, and put the failure on
    // screen rather than only in the console if the fetch itself fails.
    load: function (container, url, options) {
      return global.fetch(url, { cache: 'no-store' })
        .then(function (response) {
          if (!response.ok) { throw new Error(response.status + ' ' + response.statusText); }
          return response.text();
        })
        .then(function (text) {
          DigestRender.render(container, text, options);
        })
        .catch(function (err) {
          container.innerHTML = '';
          container.classList.add('digest-canvas');
          rawDump(container, String(err), '// could not load ' + url);
        });
    }
  };

  global.DigestRender = DigestRender;
})(window);