#include "EquationLayout.jsxinc"

(function () {
  function codePoint(value) {
    if (value <= 0xFFFF) return String.fromCharCode(value);
    var adjusted = value - 0x10000;
    return String.fromCharCode(0xD800 + (adjusted >> 10), 0xDC00 + (adjusted & 0x3FF));
  }

  var italicLower = {
    a: 0x1D44E, b: 0x1D44F, c: 0x1D450, d: 0x1D451, e: 0x1D452, f: 0x1D453, g: 0x1D454,
    h: 0x210E, i: 0x1D456, j: 0x1D457, k: 0x1D458, l: 0x1D459, m: 0x1D45A, n: 0x1D45B,
    o: 0x1D45C, p: 0x1D45D, q: 0x1D45E, r: 0x1D45F, s: 0x1D460, t: 0x1D461, u: 0x1D462,
    v: 0x1D463, w: 0x1D464, x: 0x1D465, y: 0x1D466, z: 0x1D467,
  };
  var italicUpper = {
    A: 0x1D434, B: 0x1D435, C: 0x1D436, D: 0x1D437, E: 0x1D438, F: 0x1D439, G: 0x1D43A,
    H: 0x1D43B, I: 0x1D43C, J: 0x1D43D, K: 0x1D43E, L: 0x1D43F, M: 0x1D440, N: 0x1D441,
    O: 0x1D442, P: 0x1D443, Q: 0x1D444, R: 0x1D445, S: 0x1D446, T: 0x1D447, U: 0x1D448,
    V: 0x1D449, W: 0x1D44A, X: 0x1D44B, Y: 0x1D44C, Z: 0x1D44D,
  };

  function mathText(value, upright) {
    if (upright) return value;
    var result = '';
    for (var i = 0; i < value.length; i += 1) {
      var char = value.charAt(i);
      if (italicLower[char]) result += codePoint(italicLower[char]);
      else if (italicUpper[char]) result += codePoint(italicUpper[char]);
      else result += char;
    }
    return result;
  }

  function blackColor() {
    var color = new RGBColor();
    color.red = 0;
    color.green = 0;
    color.blue = 0;
    return color;
  }

  function findCambriaMath() {
    var names = ['Cambria Math', 'CambriaMath'];
    for (var n = 0; n < names.length; n += 1) {
      try { return app.textFonts.getByName(names[n]); } catch (ignore) {}
    }
    for (var i = 0; i < app.textFonts.length; i += 1) {
      var font = app.textFonts[i];
      if (/cambria\s*math/i.test(font.name || '') || /cambria\s*math/i.test(font.family || '')) return font;
    }
    throw new Error('Cambria Math was not found. Install Cambria Math before running this script.');
  }

  function addText(doc, group, font, color, value, x, baseline, size, upright) {
    var frame = doc.textFrames.pointText([x, baseline]);
    frame.contents = mathText(value, upright);
    frame.textRange.characterAttributes.textFont = font;
    frame.textRange.characterAttributes.size = size;
    frame.textRange.characterAttributes.fillColor = color;
    frame.move(group, ElementPlacement.PLACEATEND);
    return frame;
  }

  function addLine(doc, group, color, x1, y, x2, width) {
    var line = doc.pathItems.add();
    line.setEntirePath([[x1, y], [x2, y]]);
    line.stroked = true;
    line.strokeWidth = Math.max(0.6, width);
    line.strokeColor = color;
    line.filled = false;
    line.move(group, ElementPlacement.PLACEATEND);
    return line;
  }

  function renderNode(doc, group, font, color, node, x, baseline, size) {
    var measured = EquationLayout.measure(node, size);
    var cursor = x;
    if (!node) return measured;
    if (node.type === 'text') {
      addText(doc, group, font, color, node.text, cursor, baseline, size, node.upright);
      cursor += measured.width;
    } else if (node.type === 'row') {
      var lineX = cursor;
      var lineBaseline = baseline;
      (node.children || []).forEach(function (child) {
        if (child.type === 'break') {
          lineX = x;
          lineBaseline -= size * 1.35;
        } else {
          var childMeasure = EquationLayout.measure(child, size);
          renderNode(doc, group, font, color, child, lineX, lineBaseline, size);
          lineX += childMeasure.width;
        }
      });
    } else if (node.type === 'frac') {
      var numerator = EquationLayout.measure(node.numerator, size * 0.82);
      var denominator = EquationLayout.measure(node.denominator, size * 0.82);
      var innerWidth = Math.max(numerator.width, denominator.width);
      renderNode(doc, group, font, color, node.numerator, cursor + (innerWidth - numerator.width) / 2 + size * 0.25, baseline + size * 0.5, size * 0.82);
      renderNode(doc, group, font, color, node.denominator, cursor + (innerWidth - denominator.width) / 2 + size * 0.25, baseline - size * 0.58, size * 0.82);
      addLine(doc, group, color, cursor + size * 0.2, baseline + size * 0.12, cursor + measured.width - size * 0.2, Math.max(0.8, size / 18));
    } else if (node.type === 'sqrt') {
      addText(doc, group, font, color, '√', cursor, baseline, size, true);
      renderNode(doc, group, font, color, node.value, cursor + size * 0.62, baseline, size * 0.88);
      addLine(doc, group, color, cursor + size * 0.6, baseline + size * 0.72, cursor + measured.width, Math.max(0.7, size / 22));
    } else if (node.type === 'overline') {
      renderNode(doc, group, font, color, node.value, cursor, baseline, size);
      addLine(doc, group, color, cursor, baseline + size * 0.88, cursor + measured.width, Math.max(0.7, size / 22));
    }

    if (node.sup) renderNode(doc, group, font, color, node.sup, x + measured.width - EquationLayout.measure(node.sup, size * 0.68).width * 0.82, baseline + size * 0.58, size * 0.68);
    if (node.sub) renderNode(doc, group, font, color, node.sub, x + measured.width - EquationLayout.measure(node.sub, size * 0.68).width * 0.82, baseline - size * 0.52, size * 0.68);
    return measured;
  }

  function extractLatex(value) {
    var source = String(value || '').replace(/^\s+|\s+$/g, '');
    if (source.charAt(0) === '{') {
      try {
        var parsed = JSON.parse(source);
        source = parsed.latex || parsed.tex || parsed.equation || source;
      } catch (ignore) {}
    }
    return source;
  }

  function decodeHtml(value) {
    return String(value || '').replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, '&');
  }

  function fromClipboard() {
    var html = '';
    try { html = system.callSystem('/usr/bin/pbpaste -Prefer html'); } catch (ignore) {}
    var match = html.match(/data-equation-latex="([^"]+)"/i);
    if (match) {
      try { return decodeURIComponent(decodeHtml(match[1])); } catch (ignoreDecode) { return decodeHtml(match[1]); }
    }
    try { return system.callSystem('/usr/bin/pbpaste'); } catch (ignorePlain) { return ''; }
  }

  function readFile(file) {
    file.encoding = 'UTF-8';
    file.open('r');
    var value = file.read();
    file.close();
    return value;
  }

  function renderEquation(latex, size) {
    if (!app.documents.length) app.documents.add();
    var doc = app.activeDocument;
    var artboard = doc.artboards[doc.artboards.getActiveArtboardIndex()].artboardRect;
    var ast = EquationLayout.parse(extractLatex(latex));
    var measured = EquationLayout.measure(ast, size);
    var group = doc.groupItems.add();
    group.name = 'Editable Equation - Cambria Math';
    var color = blackColor();
    var left = (artboard[0] + artboard[2] - measured.width) / 2;
    var centerY = (artboard[1] + artboard[3]) / 2;
    var baseline = centerY - (measured.ascent - measured.descent) / 2;
    renderNode(doc, group, findCambriaMath(), color, ast, left, baseline, size);
    group.selected = true;
    return group;
  }

  function showDialog() {
    var dialog = new Window('dialog', 'Equation to Editable Illustrator Text');
    dialog.orientation = 'column';
    dialog.alignChildren = ['fill', 'top'];
    var help = dialog.add('statictext', undefined, 'Paste LaTeX or choose a .tex/.json file. Output is editable Cambria Math text objects.');
    help.alignment = 'fill';
    var input = dialog.add('edittext', undefined, '', { multiline: true, scrolling: true });
    input.preferredSize = [620, 220];
    var sizeGroup = dialog.add('group');
    sizeGroup.add('statictext', undefined, 'Base size (pt):');
    var sizeInput = sizeGroup.add('edittext', undefined, '36');
    sizeInput.characters = 6;
    var actions = dialog.add('group');
    actions.alignment = 'right';
    var paste = actions.add('button', undefined, 'Paste Clipboard');
    var open = actions.add('button', undefined, 'Open .tex / .json');
    var create = actions.add('button', undefined, 'Create Editable Equation');
    var cancel = actions.add('button', undefined, 'Cancel');

    input.text = fromClipboard();
    paste.onClick = function () { input.text = fromClipboard(); };
    open.onClick = function () {
      var file = File.openDialog('Choose a LaTeX or JSON file');
      if (file) input.text = readFile(file);
    };
    create.onClick = function () {
      try {
        var value = input.text;
        if (!value) throw new Error('Paste LaTeX or choose a file first.');
        var size = Number(sizeInput.text) || 36;
        dialog.close();
        renderEquation(value, size);
      } catch (error) {
        alert(error.message);
      }
    };
    cancel.onClick = function () { dialog.close(); };
    dialog.show();
  }

  showDialog();
}());
