/**
 * InDesign ExtendScript
 * Split OR Dissolve the currently selected graphic frame.
 * - Split: regular grid split (rows/cols/gaps) keeping image in same absolute position.
 * - Dissolve: creates particle tiles (size-based) and dissolves them with wind/gravity/strength.
 *
 * Select ONE graphic frame containing a placed image, then run.
 * 
 * By: Mohammad Sadeea - sadeea.de
 */

(function () {
  if (app.documents.length === 0) { alert("No document open."); return; }
  if (app.selection.length !== 1) { alert("Select exactly ONE graphic frame that contains a placed image."); return; }

  var frame = app.selection[0];
  if (!frame) {
    alert("The selected item must be a frame.");
    return;
  }

  // ---------- helpers ----------
  function toInt(v, label) {
    var n = parseInt(v, 10);
    if (isNaN(n) || n < 1) throw new Error(label + " must be an integer ≥ 1.");
    return n;
  }

  function toNumber(v, label) {
    var n = Number(v);
    if (isNaN(n)) throw new Error(label + " must be a number.");
    return n;
  }

  function toPoints(v, label) {
    var s = (v + "").replace(/^\s+|\s+$/g, "");
    if (s === "") return 0;
    try {
      if (!/[a-zA-Z]/.test(s)) s = s + " pt";
      return UnitValue(s).as("pt");
    } catch (e) {
      throw new Error(label + " is not a valid measurement: " + v);
    }
  }

  function clamp(n, a, b) { return Math.max(a, Math.min(b, n)); }
  function rand() { return Math.random(); }
  function randRange(a, b) { return a + (b - a) * rand(); }

  // ---------- UI ----------
  var w = new Window("dialog", "Split / Dissolve Graphic Frame");
  w.alignChildren = "fill";

  var modeP = w.add("panel", undefined, "Mode");
  modeP.orientation = "row";
  modeP.alignChildren = "left";
  modeP.add("statictext", undefined, "Mode:");
  var modeDd = modeP.add("dropdownlist", undefined, ["Split Grid", "Dissolve"]);
  modeDd.selection = 0;

  // Split options
  var splitP = w.add("panel", undefined, "Split options");
  splitP.orientation = "column";
  splitP.alignChildren = "left";

  var rowGrp = splitP.add("group");
  rowGrp.add("statictext", undefined, "Rows:");
  var rowsEt = rowGrp.add("edittext", undefined, "2"); rowsEt.characters = 8;

  var colGrp = splitP.add("group");
  colGrp.add("statictext", undefined, "Columns:");
  var colsEt = colGrp.add("edittext", undefined, "2"); colsEt.characters = 8;

  var gapXGrp = splitP.add("group");
  gapXGrp.add("statictext", undefined, "Horizontal gap:");
  var gapXEt = gapXGrp.add("edittext", undefined, "0"); gapXEt.characters = 12;
  gapXGrp.add("statictext", undefined, "(e.g. 2 mm, 6 pt, 0)");

  var gapYGrp = splitP.add("group");
  gapYGrp.add("statictext", undefined, "Vertical gap:");
  var gapYEt = gapYGrp.add("edittext", undefined, "0"); gapYEt.characters = 12;
  gapYGrp.add("statictext", undefined, "(e.g. 2 mm, 6 pt, 0)");

  // Dissolve options
  var disP = w.add("panel", undefined, "Dissolve options");
  disP.orientation = "column";
  disP.alignChildren = "left";

  var strengthGrp = disP.add("group");
  strengthGrp.add("statictext", undefined, "Strength (0–100):");
  var strengthEt = strengthGrp.add("edittext", undefined, "70"); strengthEt.characters = 6;

  var windGrp = disP.add("group");
  windGrp.add("statictext", undefined, "Wind (X drift):");
  var windEt = windGrp.add("edittext", undefined, "40 pt"); windEt.characters = 12;
  windGrp.add("statictext", undefined, "(+ right / - left)");

  var gravGrp = disP.add("group");
  gravGrp.add("statictext", undefined, "Gravity (Y drift):");
  var gravEt = gravGrp.add("edittext", undefined, "10 pt"); gravEt.characters = 12;
  gravGrp.add("statictext", undefined, "(+ down / - up)");

  var sizeGrp = disP.add("group");
  sizeGrp.add("statictext", undefined, "Particle size:");
  var sizeEt = sizeGrp.add("edittext", undefined, "6 pt"); sizeEt.characters = 12;
  sizeGrp.add("statictext", undefined, "(bigger = faster)");

  var disGapGrp = disP.add("group");
  disGapGrp.add("statictext", undefined, "Particle gap:");
  var disGapEt = disGapGrp.add("edittext", undefined, "0"); disGapEt.characters = 12;

  // shrink dissolved tiles option
  var shrinkGrp = disP.add("group");
  shrinkGrp.add("statictext", undefined, "Shrink dissolved tiles (0–100%):");
  var shrinkEt = shrinkGrp.add("edittext", undefined, "60"); shrinkEt.characters = 6;
  shrinkGrp.add("statictext", undefined, "(0=none, 100=shrink a lot)");

  var fadeChk = disP.add("checkbox", undefined, "Fade particles as they dissolve");
  fadeChk.value = true;

  var keepOrigChk = w.add("checkbox", undefined, "Keep original frame");
  keepOrigChk.value = false;

  function syncPanels() {
    var isSplit = (modeDd.selection.index === 0);
    splitP.enabled = isSplit;
    disP.enabled = !isSplit;
  }
  modeDd.onChange = syncPanels;
  syncPanels();

  var btns = w.add("group");
  btns.alignment = "right";
  btns.add("button", undefined, "Cancel", { name: "cancel" });
  btns.add("button", undefined, "OK", { name: "ok" });

  if (w.show() !== 1) return;

  // ---------- read options ----------
  var mode = modeDd.selection.index; // 0 split, 1 dissolve
  var rows, cols, gapX, gapY;

  var strength, wind, gravity, pSize, pGap, doFade, shrinkAmt;

  try {
    if (mode === 0) {
      rows = toInt(rowsEt.text, "Rows");
      cols = toInt(colsEt.text, "Columns");
      gapX = toPoints(gapXEt.text, "Horizontal gap");
      gapY = toPoints(gapYEt.text, "Vertical gap");
    } else {
      strength = clamp(toNumber(strengthEt.text, "Strength"), 0, 100) / 100; // 0..1
      wind = toPoints(windEt.text, "Wind");
      gravity = toPoints(gravEt.text, "Gravity");
      pSize = toPoints(sizeEt.text, "Particle size");
      pGap = toPoints(disGapEt.text, "Particle gap");
      doFade = !!fadeChk.value;

      // NEW: 0..1
      shrinkAmt = clamp(toNumber(shrinkEt.text, "Shrink dissolved tiles"), 0, 100) / 100;

      if (pSize <= 0) throw new Error("Particle size must be > 0.");
    }
  } catch (err) {
    alert(err.message);
    return;
  }

  // ---------- main ----------
  app.doScript(function () {
    var oldRedraw = app.scriptPreferences.enableRedraw;
    var oldUI = app.scriptPreferences.userInteractionLevel;
    app.scriptPreferences.enableRedraw = false;
    app.scriptPreferences.userInteractionLevel = UserInteractionLevels.NEVER_INTERACT;

    var gb = frame.geometricBounds; // [top,left,bottom,right]
    var top = gb[0], left = gb[1], bottom = gb[2], right = gb[3];
    var totalW = right - left;
    var totalH = bottom - top;
    
    function makeCell(geoBounds) {
      var p = frame.parent ? frame.parent : null;
      var dup = frame.duplicate(p);
      dup.itemLayer = frame.itemLayer;
      dup.geometricBounds = geoBounds;
      return dup;
    }

    // ----- Split mode -----
    if (mode === 0) {
      var usableW = totalW - (cols - 1) * gapX;
      var usableH = totalH - (rows - 1) * gapY;
      if (usableW <= 0 || usableH <= 0) throw new Error("Gaps are too large for the frame size.");

      var cellW = usableW / cols;
      var cellH = usableH / rows;

      for (var r = 0; r < rows; r++) {
        for (var c = 0; c < cols; c++) {
          var cellTop = top + r * (cellH + gapY);
          var cellLeft = left + c * (cellW + gapX);
          makeCell([cellTop, cellLeft, cellTop + cellH, cellLeft + cellW]);
        }
      }

      if (!keepOrigChk.value) frame.remove();
      return;
    }

    // ----- Dissolve mode -----
    // Determine cols/rows from particle size
    var stepX = pSize + pGap;
    var stepY = pSize + pGap;
    var colsD = Math.max(1, Math.floor((totalW + pGap) / stepX));
    var rowsD = Math.max(1, Math.floor((totalH + pGap) / stepY));

    // Recompute actual cell size to fit cleanly across the frame
    var usableWD = totalW - (colsD - 1) * pGap;
    var usableHD = totalH - (rowsD - 1) * pGap;
    var cellWD = usableWD / colsD;
    var cellHD = usableHD / rowsD;

    var count = colsD * rowsD;
    if (count > 2000) {
      alert("Warning: This will create about " + count + " particles and may be slow.\nTry a larger Particle size for better performance.");
      return;
    }

    for (var rr = 0; rr < rowsD; rr++) {
      for (var cc = 0; cc < colsD; cc++) {
        var xNorm = (colsD === 1) ? 0 : (cc / (colsD - 1)); // 0..1 left->right
        var dissolveProb = clamp(Math.pow(xNorm, 1.4) * strength, 0, 1);

        var cellTopD = top + rr * (cellHD + pGap);
        var cellLeftD = left + cc * (cellWD + pGap);
        var tile = makeCell([cellTopD, cellLeftD, cellTopD + cellHD, cellLeftD + cellWD]);

        if (rand() < dissolveProb) {
          // Dissolved tiles: drift with wind/gravity, plus randomness
          var mag = dissolveProb; // 0..1

          var dx = wind * mag * randRange(0.3, 1.2) + randRange(-0.35, 0.35) * wind * mag;
          var dy = gravity * mag * randRange(0.3, 1.2) + randRange(-0.35, 0.35) * gravity * mag;

          tile.move(undefined, [dx, dy]);

          // ------------------------------------------------------------
          // Shrink increases "downwind": farther downwind = smaller.
          // Works for wind positive (right) OR negative (left).
          // Tiles that drift upwind (against wind) don't get wind-shrunk.
          // ------------------------------------------------------------

          // Estimate max possible downwind drift (based on how dx is computed)
          var windAbs = Math.abs(wind);
          var maxDx = windAbs * strength * 1.55; // 1.2 + 0.35 worst-case multiplier

          // Downwind projection: normalize dx so "downwind" is positive.
          // If wind>0: downwind is +dx. If wind<0: downwind is -dx.
          var downwind = 0;
          if (windAbs > 0.0001 && maxDx > 0.0001) {
            downwind = (wind >= 0) ? dx : (-dx);
            downwind = clamp(downwind / maxDx, 0, 1); // 0..1
          } else {
            // No wind: fall back to dissolve amount (keeps behavior sensible)
            downwind = mag;
          }

          // Base "speckle" shrink (your original behavior, kept)
          var speckle = 1 - (mag * randRange(0.10, 0.55)); // 0.45..0.90-ish

          // wind-based shrink (0..shrinkAmt), scaled by how far downwind it went
          // shrinkAmt is 0..1 from UI
          var windShrink = 1 - (shrinkAmt * downwind);
          windShrink = clamp(windShrink, 0.05, 1); // never below 5%

          var finalScale = shrinkAmt > 0 ? speckle * windShrink : 1;

          try {
            tile.resize(
              CoordinateSpaces.PASTEBOARD_COORDINATES,
              AnchorPoint.CENTER_ANCHOR,
              ResizeMethods.MULTIPLYING_CURRENT_DIMENSIONS_BY,
              [finalScale, finalScale]
            );
          } catch (e) { }

          // optional fade out
          if (doFade) {
            try {
              tile.transparencySettings.blendingSettings.opacity =
                Math.round(100 * (1 - mag * randRange(0.3, 0.95)));
            } catch (e2) { }
          }
        } else {
          // Solid tiles...
          if (doFade) {
            try { tile.transparencySettings.blendingSettings.opacity = 100; } catch (e3) { }
          }
        }

      }
    }

    if (!keepOrigChk.value) frame.remove();

    app.scriptPreferences.enableRedraw = oldRedraw;
    app.scriptPreferences.userInteractionLevel = oldUI;

  }, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, "Split/Dissolve Frame");

})();
