// Diff Viewer — line-by-line text diff with unified output, stats, and copy.

(function () {
  // LCS-based line diff. Returns an array of { type, line } where type is
  // 'add', 'remove', or 'same'. Uses a standard dynamic-programming LCS table
  // to produce a minimal edit sequence.
  var MAX_DIFF_LINES = 5000;

  function computeDiff(oldLines, newLines) {
    var m = oldLines.length;
    var n = newLines.length;

    // Guard against O(m*n) explosion on large inputs
    if (m > MAX_DIFF_LINES || n > MAX_DIFF_LINES) {
      return [{ type: 'same', line: '(Diff disabled: inputs exceed ' + MAX_DIFF_LINES + ' lines. Paste shorter text.)' }];
    }

    // Build LCS length table
    var dp = new Array(m + 1);
    for (var i = 0; i <= m; i++) {
      dp[i] = new Array(n + 1);
      dp[i][0] = 0;
    }
    for (var j = 0; j <= n; j++) dp[0][j] = 0;

    for (var i = 1; i <= m; i++) {
      for (var j = 1; j <= n; j++) {
        if (oldLines[i - 1] === newLines[j - 1]) {
          dp[i][j] = dp[i - 1][j - 1] + 1;
        } else {
          dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
        }
      }
    }

    // Back-trace to produce diff entries
    var result = [];
    var i = m, j = n;
    while (i > 0 || j > 0) {
      if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
        result.push({ type: 'same', oldLine: i, newLine: j, text: oldLines[i - 1] });
        i--; j--;
      } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
        result.push({ type: 'add', oldLine: null, newLine: j, text: newLines[j - 1] });
        j--;
      } else {
        result.push({ type: 'remove', oldLine: i, newLine: null, text: oldLines[i - 1] });
        i--;
      }
    }
    result.reverse();
    return result;
  }

  var EXAMPLE_ORIGINAL = [
    'function greet(name) {',
    '  console.log("Hello, " + name);',
    '  return true;',
    '}',
    '',
    'greet("World");',
  ].join('\n');

  var EXAMPLE_MODIFIED = [
    'function greet(name, loud) {',
    '  const msg = `Hello, ${name}!`;',
    '  if (loud) console.log(msg.toUpperCase());',
    '  else console.log(msg);',
    '  return true;',
    '}',
    '',
    'greet("World", true);',
  ].join('\n');

  window.TOOL_HANDLERS['diff-viewer'] = function DiffViewerTool() {
    var _React = React;
    var useState = _React.useState;
    var useMemo = _React.useMemo;
    var useCallback = _React.useCallback;

    var _s = useState(EXAMPLE_ORIGINAL);
    var original = _s[0], setOriginal = _s[1];
    var _m = useState(EXAMPLE_MODIFIED);
    var modified = _m[0], setModified = _m[1];
    var _c = useState(false);
    var copied = _c[0], setCopied = _c[1];
    var _d = useState(true);
    var showDiff = _d[0], setShowDiff = _d[1];
    // diffVersion increments only when user clicks Compare — prevents O(m*n) on every keystroke
    var _dv = useState(0);
    var diffVersion = _dv[0], setDiffVersion = _dv[1];

    var diff = useMemo(function () {
      if (!showDiff) return [];
      var oldLines = original.split('\n');
      var newLines = modified.split('\n');
      return computeDiff(oldLines, newLines);
    }, [diffVersion, showDiff]);

    var stats = useMemo(function () {
      var added = 0, removed = 0, unchanged = 0;
      for (var i = 0; i < diff.length; i++) {
        if (diff[i].type === 'add') added++;
        else if (diff[i].type === 'remove') removed++;
        else unchanged++;
      }
      return { added: added, removed: removed, unchanged: unchanged };
    }, [diff]);

    var unifiedText = useMemo(function () {
      var lines = ['--- Original', '+++ Modified'];
      for (var i = 0; i < diff.length; i++) {
        var d = diff[i];
        if (d.type === 'add') lines.push('+' + d.text);
        else if (d.type === 'remove') lines.push('-' + d.text);
        else lines.push(' ' + d.text);
      }
      return lines.join('\n');
    }, [diff]);

    var handleSwap = useCallback(function () {
      var tmp = original;
      setOriginal(modified);
      setModified(tmp);
    }, [original, modified]);

    var handleClear = useCallback(function () {
      setOriginal('');
      setModified('');
    }, []);

    var handleCopy = useCallback(function () {
      navigator.clipboard.writeText(unifiedText).then(function () {
        setCopied(true);
        setTimeout(function () { setCopied(false); }, 1800);
      });
    }, [unifiedText]);

    // Inline styles using CSS variables for dark-mode compatibility
    var containerStyle = {
      display: 'grid',
      gridTemplateColumns: '1fr 1fr',
      gap: 12,
    };

    var textareaStyle = {
      minHeight: 200,
      fontFamily: 'var(--id-font-mono)',
      fontSize: 13,
      lineHeight: '1.5',
      resize: 'vertical',
    };

    var actionsStyle = {
      display: 'flex',
      gap: 8,
      marginTop: 14,
      flexWrap: 'wrap',
      alignItems: 'center',
    };

    var statsBarStyle = {
      display: 'flex',
      gap: 16,
      marginTop: 16,
      marginBottom: 8,
      fontSize: 13,
      fontWeight: 600,
    };

    var diffContainerStyle = {
      marginTop: 12,
      border: '1px solid var(--id-border)',
      borderRadius: 10,
      overflow: 'hidden',
      background: 'var(--id-surface)',
    };

    var diffTableStyle = {
      width: '100%',
      borderCollapse: 'collapse',
      fontFamily: 'var(--id-font-mono)',
      fontSize: 12,
      lineHeight: '1.55',
      tableLayout: 'fixed',
    };

    return (
      <div className="mini-tool">
        {/* Input panels */}
        <div style={containerStyle}>
          <div>
            <label className="mini-label">Original</label>
            <textarea
              className="mini-input mini-textarea"
              style={textareaStyle}
              placeholder="Paste original text here..."
              value={original}
              onChange={function (e) { setOriginal(e.target.value); }}
            />
          </div>
          <div>
            <label className="mini-label">Modified</label>
            <textarea
              className="mini-input mini-textarea"
              style={textareaStyle}
              placeholder="Paste modified text here..."
              value={modified}
              onChange={function (e) { setModified(e.target.value); }}
            />
          </div>
        </div>

        {/* Action buttons */}
        <div style={actionsStyle}>
          <button className="btn btn-primary" onClick={function () { setShowDiff(true); setDiffVersion(function (v) { return v + 1; }); }}>
            <window.Icon name="check" size={15} /> Compare
          </button>
          <button className="btn btn-secondary" onClick={handleSwap}>
            <window.Icon name="swap" size={15} /> Swap
          </button>
          <button className="btn btn-secondary" onClick={handleClear}>
            <window.Icon name="x" size={15} /> Clear
          </button>
          {diff.length > 0 && (
            <button className="btn btn-secondary" onClick={handleCopy}>
              <window.Icon name={copied ? 'check' : 'doc'} size={15} />{' '}
              {copied ? 'Copied!' : 'Copy diff'}
            </button>
          )}
        </div>

        {/* Stats */}
        {diff.length > 0 && (
          <div style={statsBarStyle}>
            <span style={{ color: '#16a34a' }}>+{stats.added} added</span>
            <span style={{ color: '#dc2626' }}>&minus;{stats.removed} removed</span>
            <span style={{ color: 'var(--id-text-muted)' }}>{stats.unchanged} unchanged</span>
          </div>
        )}

        {/* Diff output */}
        {diff.length > 0 && (function () {
          // Precompute style objects outside the loop to avoid per-row allocation
          var lineNumBase = { width: 48, minWidth: 48, padding: '1px 8px 1px 10px', textAlign: 'right', color: 'var(--id-text-muted)', fontSize: 11, userSelect: 'none', borderRight: '1px solid var(--id-border)', verticalAlign: 'top', opacity: 0.7 };
          var textBase = { padding: '1px 10px', whiteSpace: 'pre-wrap', wordBreak: 'break-all', color: 'var(--id-text)', verticalAlign: 'top' };
          var prefixBase = { width: 20, minWidth: 20, padding: '1px 4px', textAlign: 'center', fontWeight: 700, userSelect: 'none', verticalAlign: 'top' };
          var rowStyles = {
            add: { bg: { background: 'rgba(22, 163, 74, 0.10)' }, prefix: Object.assign({}, prefixBase, { color: '#16a34a' }), chr: '+' },
            remove: { bg: { background: 'rgba(220, 38, 38, 0.10)' }, prefix: Object.assign({}, prefixBase, { color: '#dc2626' }), chr: '\u2212' },
            same: { bg: { background: 'transparent' }, prefix: Object.assign({}, prefixBase, { color: 'var(--id-text-muted)' }), chr: ' ' },
          };
          return (
            <div style={diffContainerStyle}>
              <div style={{ overflowX: 'auto' }}>
                <table style={diffTableStyle}>
                  <tbody>
                    {diff.map(function (entry, idx) {
                      var s = rowStyles[entry.type] || rowStyles.same;
                      return (
                        <tr key={idx} style={s.bg}>
                          <td style={lineNumBase}>{entry.oldLine || ''}</td>
                          <td style={lineNumBase}>{entry.newLine || ''}</td>
                          <td style={s.prefix}>{s.chr}</td>
                          <td style={textBase}>{entry.text}</td>
                        </tr>
                      );
                    })}
                  </tbody>
                </table>
              </div>
            </div>
          );
        })()}

        <style>{`
          @media (max-width: 600px) {
            .mini-tool > div:first-child { grid-template-columns: 1fr !important; }
          }
        `}</style>
      </div>
    );
  };
})();
