// ISO_WorkAreaEndToFootageEnd.jsx
// Artist: https://www.jasonfletcher.info/
// Code generated by ChatGPT-5
// Date: October 9, 2025
// Description: This After Effects script will move the "Work Area End" to the end of the contained footage of the comp. This will be applied to the comps that are selected in the Project window.

/*
  Move Work Area End to Longest Footage End (Fixed)
  - Applies to all selected CompItems in the Project panel.
  - Finds the maximum layer.outPoint for layers whose source is a FootageItem.
  - Snaps the resulting Work Area End to the nearest frame boundary.
  - Leaves workAreaStart unchanged unless the new end <= current start, in which case workAreaStart is moved to 0 and duration is set to the snapped end (min 1 frame).
*/

(function moveWorkAreaEndToLongestFootage() {
    app.beginUndoGroup("Move Work Area End to Longest Footage End");

    var sel = app.project.selection;
    if (!sel || sel.length === 0) {
        alert("No items selected. Please select one or more compositions in the Project panel.");
        return;
    }

    var updated = [];
    var skipped = [];

    for (var i = 0; i < sel.length; i++) {
        var item = sel[i];

        if (!(item instanceof CompItem)) {
            skipped.push((item && item.name) ? (item.name + " (not a composition)") : "Unnamed item (not a composition)");
            continue;
        }

        var comp = item;
        var maxEnd = -Infinity;

        // Iterate layers (1-based)
        for (var li = 1; li <= comp.numLayers; li++) {
            try {
                var layer = comp.layer(li);
                if (!layer) continue;

                // Only consider layers whose source is a FootageItem (images, video, sequences)
                if (layer.source && (layer.source instanceof FootageItem)) {
                    var endTime = layer.outPoint; // outPoint is in comp time
                    if (isFinite(endTime) && endTime > maxEnd) {
                        maxEnd = endTime;
                    }
                }
            } catch (e) {
                // ignore layer errors and continue
            }
        }

        if (maxEnd === -Infinity) {
            skipped.push(comp.name + " (no footage layers found)");
            continue;
        }

        // Snap the end to the next frame boundary (so work area lines up to frames)
        var frameDur = comp.frameDuration;
        var eps = 1e-6;
        var endFrameCount = Math.ceil((maxEnd + eps) / frameDur); // ceil to include the frame containing maxEnd
        var snappedMaxEnd = endFrameCount * frameDur;

        // Compute new duration relative to current workAreaStart
        var workStart = comp.workAreaStart;
        var newDuration = snappedMaxEnd - workStart;

        if (newDuration <= 0) {
            // If the longest footage ends at/ before current work start,
            // move start to 0 and set duration to snappedMaxEnd (min 1 frame)
            comp.workAreaStart = 0;
            comp.workAreaDuration = Math.max(frameDur, snappedMaxEnd);
        } else {
            comp.workAreaDuration = Math.max(frameDur, newDuration);
        }

        updated.push(comp.name + " → workAreaEnd = " + snappedMaxEnd.toFixed(3) + "s");
    }

    var msg = "";
    if (updated.length) msg += "Comps Updated:\n" + updated.join("\n") + "\n\n";
    if (skipped.length) msg += "Comps Skipped:\n" + skipped.join("\n");
    alert(msg);

    app.endUndoGroup();
})();
