// ISO_RandomizePositionWithinRange.jsx
// Artist: https://www.jasonfletcher.info/
// Code generated by ChatGPT-5.5
// Date: July 16, 2026
// Description: This After Effects script will randomize the X and Y Position of the selected layers within user-defined ranges.

(function () {

    app.beginUndoGroup("Randomize X/Y Position");

    try {

        var activeItem = app.project.activeItem;
        var selectedLayers;
        var dlg;
        var grp;
        var row;
        var result;
        var minX, maxX, minY, maxY;
        var i;
        var layer;
        var pos;
        var newPos;
        var processed = 0;
        var skipped = 0;
        var currentTime;
        var keyIndex;

        if (!(activeItem instanceof CompItem)) {
            alert("Please open and activate a composition.");
            return;
        }

        selectedLayers = activeItem.selectedLayers;

        if (selectedLayers.length === 0) {
            alert("Please select one or more layers.");
            return;
        }

        // ---------------------------------------------------------------------
        // GUI
        // ---------------------------------------------------------------------

        dlg = new Window("dialog", "Randomize X/Y Position");

        grp = dlg.add("group");
        grp.orientation = "column";
        grp.alignChildren = ["fill", "top"];

        row = grp.add("group");
        row.add("statictext", undefined, "Minimum X:");
        var txtMinX = row.add("edittext", undefined, "0");
        txtMinX.characters = 10;

        row = grp.add("group");
        row.add("statictext", undefined, "Maximum X:");
        var txtMaxX = row.add("edittext", undefined, String(Math.round(activeItem.width)));
        txtMaxX.characters = 10;

        row = grp.add("group");
        row.add("statictext", undefined, "Minimum Y:");
        var txtMinY = row.add("edittext", undefined, "0");
        txtMinY.characters = 10;

        row = grp.add("group");
        row.add("statictext", undefined, "Maximum Y:");
        var txtMaxY = row.add("edittext", undefined, String(Math.round(activeItem.height)));
        txtMaxY.characters = 10;

        var btnGroup = dlg.add("group");
        btnGroup.alignment = "center";

        btnGroup.add("button", undefined, "OK", { name: "ok" });
        btnGroup.add("button", undefined, "Cancel", { name: "cancel" });

        result = dlg.show();

        if (result !== 1) {
            return;
        }

        minX = parseFloat(txtMinX.text);
        maxX = parseFloat(txtMaxX.text);
        minY = parseFloat(txtMinY.text);
        maxY = parseFloat(txtMaxY.text);

        if (isNaN(minX) || isNaN(maxX) || isNaN(minY) || isNaN(maxY)) {
            alert("Please enter valid numeric values.");
            return;
        }

        if (minX > maxX) {
            alert("Minimum X cannot be greater than Maximum X.");
            return;
        }

        if (minY > maxY) {
            alert("Minimum Y cannot be greater than Maximum Y.");
            return;
        }

        currentTime = activeItem.time;

        // ---------------------------------------------------------------------
        // Process Layers
        // ---------------------------------------------------------------------

        for (i = 0; i < selectedLayers.length; i++) {

            layer = selectedLayers[i];

            if (layer === null || layer.locked) {
                skipped++;
                continue;
            }

            pos = layer.property("Transform").property("Position");

            if (pos === null || pos.expressionEnabled) {
                skipped++;
                continue;
            }

            // Get the current value (interpolated if animated)
            newPos = pos.valueAtTime(currentTime, false);

            // Randomize X and Y
            newPos[0] = minX + Math.random() * (maxX - minX);
            newPos[1] = minY + Math.random() * (maxY - minY);

            if (pos.numKeys > 0) {

                keyIndex = pos.nearestKeyIndex(currentTime);

                // If a keyframe already exists at the current time, overwrite it.
                if (Math.abs(pos.keyTime(keyIndex) - currentTime) < 0.0001) {
                    pos.setValueAtKey(keyIndex, newPos);
                } else {
                    // Otherwise create a new keyframe.
                    pos.setValueAtTime(currentTime, newPos);
                }

            } else {

                // No keyframes yet, so create the first one.
                pos.setValueAtTime(currentTime, newPos);

            }

            processed++;
        }

        alert(
            "Randomize X/Y Position Complete\n\n" +
            "Layers Selected: " + selectedLayers.length + "\n" +
            "Layers Updated: " + processed + "\n" +
            "Layers Skipped: " + skipped + "\n\n" +
            "X Range: " + minX + " to " + maxX + "\n" +
            "Y Range: " + minY + " to " + maxY
        );

    } catch (err) {

        alert("Error:\n\n" + err.toString());

    } finally {

        app.endUndoGroup();

    }

})();
