// pathfinder_ops.js — il PONTE fra il motore nativo e i due file del fork che stanno
// qui accanto VERBATIM (`clipper.js`, `pathbool.js`).
//
// ⛔ QUESTO file NON è del fork: è nostro, ed è l'unico pezzo di JS di questa lane che
// abbiamo scritto. Ogni riga però è la TRASCRIZIONE di un pezzo preciso del fork, con
// il riferimento file:riga accanto — perché il punto della slice è che il pathfinder
// sia quello del Mac per COSTRUZIONE, non per somiglianza:
//
//   `js_pathfinder`            → jsadd/add.js:5652-5730     (Unite/Minus Front/Minus
//                                                            Back/Intersect/Exclude)
//   `js_pathfinder_ex`         → jsadd/add.js:5735-5850     (Divide/Trim/Merge/Crop)
//   `svge_pf_normalize`        → jsadd/add.js:2313-2330     (operandi → <path> assoluto)
//   `svgCanvas.convertToPath`  → js/svgcanvas.js:22278-22500 (forma → `d`)
//   `pathActions.resetOrientation` → js/svgcanvas.js:15928-15954 (bake del transform)
//   `$(el).attr([...])`        → js/jquery.attr.js          (stringa numerica → numero)
//
// Il DOM non c'è: al suo posto il motore passa gli attributi RAW degli operandi e
// riceve indietro delle stringhe `d`. Chi scrive sul documento è il C++, in UNA
// Transaction (pathfinder_qjs.cpp) — qui non si muta niente.
//
// Due voci native, montate dall'host su `__pf`:
//   __pf.convertPath(d)   → `svgscene::node_edit_d` = pathActions.convertPath(el,false),
//                           il serializer `d` fork-fedele già gated (F4.7, path_serialize.h)
//   __pf.parseTransform(t)→ `svgscene::parse_transform` → [a,b,c,d,e,f]
//
'use strict';

// ---------------------------------------------------------------------------
// jquery.attr.js — la lettura di un attributo come la fa il fork: una stringa
// numerica torna NUMERO, un attributo assente torna null. Tutte le formule di
// convertToPath contano su questo (senza, `cy+ry` concatenerebbe due stringhe).
// ---------------------------------------------------------------------------
function PF_attr(attrs, name) {
  var v = (attrs && Object.prototype.hasOwnProperty.call(attrs, name)) ? attrs[name] : null;
  if (v === null || v === undefined) return null;
  if (v || v === "0") { v = isNaN(v) ? v : v - 0; }
  return v;
}

// ---------------------------------------------------------------------------
// svgcanvas.js:22278 `convertToPath` — SOLO il ramo geometrico (lo `switch` su
// elem.tagName e il suo `joinSegs`), copiato numero per numero. Il resto della
// funzione (creazione del <path>, attributi da cur_shape, history) è DOM: lo fa
// il C++, e comunque js_pathfinder sovrascrive subito quelle vernici con quelle
// vere dell'operando (add.js:5674-5680).
//
// Nota sul <rect>: il fork legge x/y/w/h da `elem.getBBox()`; per un <rect> il
// bbox È x/y/width/height (nessun tratto, nessun figlio), quindi qui si leggono
// gli attributi. Un attributo assente vale 0, come il bbox.
// ---------------------------------------------------------------------------
function PF_shapeToPathD(tag, attrs) {
  var d = '';
  var joinSegs = function (segs) {
    for (var s = 0; s < segs.length; s++) {
      var l = segs[s][0], pts = segs[s][1];
      d += l;
      for (var i = 0; i < pts.length; i += 2) {
        d += (pts[i] + ',' + pts[i + 1]) + ' ';
      }
    }
  };

  // "Possibly the cubed root of 6, but 1.81 works best" (svgcanvas.js:22352)
  var num = 1.81;

  switch (tag) {
  case 'ellipse':
  case 'circle':
    var cx = PF_attr(attrs, 'cx'), cy = PF_attr(attrs, 'cy');
    var rx = PF_attr(attrs, 'rx'), ry = PF_attr(attrs, 'ry');
    if (tag === 'circle') { rx = ry = PF_attr(attrs, 'r'); }
    joinSegs([
      ['M', [(cx), (cy + ry)]],
      ['C', [(cx - rx / num), (cy + ry), (cx - rx), (cy + ry / num), (cx - rx), (cy)]],
      ['C', [(cx - rx), (cy - ry / num), (cx - rx / num), (cy - ry), (cx), (cy - ry)]],
      ['C', [(cx + rx / num), (cy - ry), (cx + rx), (cy - ry / num), (cx + rx), (cy)]],
      ['C', [(cx + rx), (cy + ry / num), (cx + rx / num), (cy + ry), (cx), (cy + ry)]],
      ['Z', []]
    ]);
    break;
  case 'path':
    d = attrs.d;
    break;
  case 'line':
    d = "M" + PF_attr(attrs, 'x1') + "," + PF_attr(attrs, 'y1') +
        "L" + PF_attr(attrs, 'x2') + "," + PF_attr(attrs, 'y2');
    break;
  case 'polyline':
  case 'polygon':
    d = "M" + (attrs.points === undefined ? null : attrs.points);
    break;
  case 'rect':
    var rrx = PF_attr(attrs, 'rx'), rry = PF_attr(attrs, 'ry');
    var x = PF_attr(attrs, 'x') || 0, y = PF_attr(attrs, 'y') || 0;
    var w = PF_attr(attrs, 'width') || 0, h = PF_attr(attrs, 'height') || 0;
    num = 4 - num;   // "Why? Because!" (svgcanvas.js:22400)
    if (!rrx && !rry) {
      joinSegs([
        ['M', [x, y]],
        ['L', [x + w, y]],
        ['L', [x + w, y + h]],
        ['L', [x, y + h]],
        ['L', [x, y]],
        ['Z', []]
      ]);
    } else {
      if (!rry) rry = rrx;
      joinSegs([
        ['M', [x, y + rry]],
        ['C', [x, y + rry / num, x + rrx / num, y, x + rrx, y]],
        ['L', [x + w - rrx, y]],
        ['C', [x + w - rrx / num, y, x + w, y + rry / num, x + w, y + rry]],
        ['L', [x + w, y + h - rry]],
        ['C', [x + w, y + h - rry / num, x + w - rrx / num, y + h, x + w - rrx, y + h]],
        ['L', [x + rrx, y + h]],
        ['C', [x + rrx / num, y + h, x, y + h - rry / num, x, y + h - rry]],
        ['L', [x, y + rry]],
        ['Z', []]
      ]);
    }
    break;
  default:
    d = '';
    break;
  }
  return d;
}

// ---------------------------------------------------------------------------
// svgcanvas.js:15928 `pathActions.resetOrientation` — la matrice entra nei punti
// e l'attributo `transform` sparisce.
//
// ⚠️ ORDINE, e la sola DEVIAZIONE dichiarata di questo file. Il fork trasforma la
// pathSegList GREZZA (comandi relativi compresi) e POI serializza con convertPath;
// applicare la matrice — traslazione inclusa — a un DELTA di un comando relativo è
// un bug del fork che deforma l'artwork. Qui si normalizza PRIMA in assoluto (la
// stessa rel→abs che convertPath fa comunque) e si trasforma dopo: per ogni path
// ASSOLUTO — cioè tutto ciò che il fork stesso ha creato o modificato, convertToPath
// compreso — i due ordini danno gli stessi byte; per un path relativo importato con
// un transform danno la geometria GIUSTA invece di quella del bug.
// Gli archi seguono il fork alla lettera: si trasforma solo l'ESTREMO, r1/r2/angolo
// restano quelli (`$.each(['',1,2])` legge solo x/y — svgcanvas.js:15942).
// ---------------------------------------------------------------------------
function PF_absSegs(d) {
  // Tokenizza un `d` GIÀ assoluto (l'output di __pf.convertPath, che emette
  // M/L/C/Q/A/S/T maiuscoli e uno `z` nudo — path_serialize.h).
  var segs = [];
  var re = /([MLCQASTZmlcqastz])([^MLCQASTZmlcqastz]*)/g;
  var m;
  while ((m = re.exec(d)) !== null) {
    var cmd = m[1];
    var nums = [];
    var re2 = /-?\d*\.?\d+(?:[eE][-+]?\d+)?/g, t;
    while ((t = re2.exec(m[2])) !== null) nums.push(parseFloat(t[0]));
    segs.push({ cmd: cmd, a: nums });
  }
  return segs;
}

function PF_applyMatrixD(d, mm) {
  var a = mm[0], b = mm[1], c = mm[2], dd = mm[3], e = mm[4], f = mm[5];
  var pt = function (x, y) { return [a * x + c * y + e, b * x + dd * y + f]; };
  var segs = PF_absSegs(d);
  var out = '';
  for (var i = 0; i < segs.length; i++) {
    var s = segs[i], v = s.a, k;
    var cmd = s.cmd;
    if (cmd === 'z' || cmd === 'Z') { out += 'z'; continue; }
    var parts = [];
    if (cmd === 'A') {
      // rx ry angle large sweep x y — solo l'estremo passa dalla matrice.
      for (var g = 0; g + 7 <= v.length; g += 7) {
        var p = pt(v[g + 5], v[g + 6]);
        parts.push(v[g] + ' ' + v[g + 1] + ' ' + v[g + 2] + ' ' + v[g + 3] + ' ' +
                   v[g + 4] + ' ' + p[0] + ',' + p[1]);
      }
      out += 'A' + parts.join(' ');
      continue;
    }
    for (k = 0; k + 1 < v.length; k += 2) {
      var q = pt(v[k], v[k + 1]);
      parts.push(q[0] + ',' + q[1]);
    }
    out += cmd + parts.join(' ');
  }
  return out;
}

// ---------------------------------------------------------------------------
// add.js:2313 `svge_pf_normalize` — ogni operando diventa un <path> ASSOLUTO con
// il transform cotto dentro. Il fork: convertToPath (se non è già un path) →
// resetOrientation (se c'è un transform) → convertPath.
// ---------------------------------------------------------------------------
function PF_normalizeOne(op) {
  var d = PF_shapeToPathD(op.tag, op.attrs || {});
  if (!d) return '';
  d = __pf.convertPath(d);
  if (!d) return '';
  var t = (op.attrs && op.attrs.transform) ? String(op.attrs.transform) : '';
  if (t) {
    var mm = __pf.parseTransform(t);
    d = __pf.convertPath(PF_applyMatrixD(d, mm));
  }
  return d;
}

function PF_normalize(argJSON) {
  var arg = JSON.parse(argJSON);
  var out = [];
  for (var i = 0; i < arg.length; i++) out.push(PF_normalizeOne(arg[i]));
  return JSON.stringify(out);
}

// ---------------------------------------------------------------------------
// add.js:5652 `js_pathfinder` — i cinque Shape Modes, anelli e basta.
//   in : { op, operands: [ { d, fillRule } ] }   (bottom-first, come il fork)
//   out: { ok, d, keeper, error }
//        `keeper` è l'indice nell'array ORIGINALE (add.js:5699:
//        `paths[0]` per subtract, `paths[last]` altrimenti — dopo l'eventuale
//        reverse di Minus Back, add.js:5686).
// ---------------------------------------------------------------------------
function PF_shapeModes(argJSON) {
  var arg = JSON.parse(argJSON);
  var op = arg.op;
  var ops = arg.operands.slice();
  var idx = [];
  for (var i = 0; i < ops.length; i++) idx.push(i);

  // add.js:5686 — 'subtract_back' (Minus Back) = subtract con l'oggetto IN CIMA
  // come soggetto e tutto quello sotto come clip.
  if (op === 'subtract_back') { ops.reverse(); idx.reverse(); op = 'subtract'; }

  var ringsOf = function (o) {
    return PathBool.dToPolygons(o.d, o.fillRule || 'nonzero');
  };
  var res = ringsOf(ops[0]);
  for (var j = 1; j < ops.length; j++) res = PathBool.operate(op, res, ringsOf(ops[j]));
  var newD = (res && res.length) ? PathBool.ringsToD(res) : "";
  if (!newD) {
    return JSON.stringify({ ok: false, error: "empty",
      keeper: (op === 'subtract') ? idx[0] : idx[idx.length - 1] });
  }
  // add.js:5699 — Illustrator tiene la vernice dell'oggetto IN CIMA per Unite/
  // Intersect/Exclude e quella IN FONDO per Subtract (Minus Front).
  var keeper = (op === 'subtract') ? idx[0] : idx[idx.length - 1];
  return JSON.stringify({ ok: true, d: newD, keeper: keeper });
}

// ---------------------------------------------------------------------------
// add.js:5735 `js_pathfinder_ex` — Divide / Trim / Merge / Crop.
//   in : { op, operands: [ { d, fillRule, fillKey } ] }   (bottom-first)
//   out: { ok, pieces: [ { src, d, noStroke } ], error }
//        `src` è l'indice dell'operando da cui il pezzo eredita la vernice e
//        davanti al quale va inserito (add.js:5772 insertBefore).
// ---------------------------------------------------------------------------
function PF_pathfinders(argJSON) {
  var arg = JSON.parse(argJSON);
  var op = arg.op;
  var ops = arg.operands;
  var n = ops.length;
  var rings = [];
  for (var i = 0; i < n; i++) rings.push(PathBool.dToPolygons(ops[i].d, ops[i].fillRule || 'nonzero'));

  var pieces = [];
  var emit = function (src, ringsRes, noStroke) {
    if (ringsRes && ringsRes.length) {
      var d = PathBool.ringsToD(ringsRes);
      if (d) pieces.push({ src: src, d: d, noStroke: !!noStroke });
    }
  };
  var unionAll = function (list) {
    if (!list.length) return [];
    var u = list[0];
    for (var k = 1; k < list.length; k++) u = PathBool.operate('unite', u, list[k]);
    return u;
  };

  if (op === 'crop') {
    // add.js:5789 — l'oggetto in cima è la forma di ritaglio.
    var top = rings[n - 1];
    for (var a = 0; a < n - 1; a++) emit(a, PathBool.operate('intersect', rings[a], top), false);
  } else if (op === 'divide') {
    // add.js:5793 — A−B (stile A), B−A (stile B), A∩B (stile dell'oggetto in cima).
    emit(0, PathBool.operate('subtract', rings[0], rings[1]), false);
    emit(1, PathBool.operate('subtract', rings[1], rings[0]), false);
    emit(1, PathBool.operate('intersect', rings[0], rings[1]), false);
  } else {   // trim | merge — add.js:5797
    var vis = [];
    for (var b = 0; b < n; b++) {
      var above = unionAll(rings.slice(b + 1));
      var v = (above && above.length) ? PathBool.operate('subtract', rings[b], above) : rings[b];
      if (v && v.length) vis.push({ src: b, rings: v });
    }
    if (op === 'merge') {
      // add.js:5806 — Merge unisce anche i pezzi adiacenti con lo stesso fill.
      var byFill = {}, order = [];
      for (var c = 0; c < vis.length; c++) {
        var f = ops[vis[c].src].fillKey || '';
        if (!byFill[f]) { byFill[f] = { src: vis[c].src, rings: vis[c].rings }; order.push(f); }
        else { byFill[f].rings = PathBool.operate('unite', byFill[f].rings, vis[c].rings); }
      }
      for (var e2 = 0; e2 < order.length; e2++) emit(byFill[order[e2]].src, byFill[order[e2]].rings, true);
    } else {
      for (var g2 = 0; g2 < vis.length; g2++) emit(vis[g2].src, vis[g2].rings, true);
    }
  }
  if (!pieces.length) return JSON.stringify({ ok: false, error: "empty" });
  return JSON.stringify({ ok: true, pieces: pieces });
}
