// ExtendScript host for the AI Edit CEP panel.
// ASCII only: ExtendScript guesses this file's encoding, so all localized
// strings are passed in from the panel instead of written here.

function aiEditHasDocument() {
    try {
        return app.documents.length > 0 ? "yes" : "no";
    } catch (e) {
        return "no";
    }
}

function aiEditShowComposite(doc) {
    try {
        doc.activeChannels = doc.componentChannels;
    } catch (e) {
        try {
            doc.activeChannels = [doc.channels[0]];
        } catch (fallbackError) {}
    }
}

function aiEditPickImages(title, filter) {
    try {
        var picked = File.openDialog(title, filter, true);
        if (!picked) return "";
        if (!(picked instanceof Array)) picked = [picked];

        var paths = [];
        for (var i = 0; i < picked.length; i++) paths.push(picked[i].fsName);
        return paths.join("\n");
    } catch (e) {
        return "";
    }
}

// A native window is the reliable way to type CJK: the CEF panel does not
// always receive IME composition events.
function aiEditPromptDialog(title, label, okText, cancelText, initial) {
    try {
        var dialog = new Window("dialog", title);
        dialog.alignChildren = "fill";
        dialog.margins = 14;
        dialog.spacing = 10;

        dialog.add("statictext", undefined, label);

        var field = dialog.add("edittext", undefined, initial || "", {
            multiline: true,
            wantReturn: true
        });
        field.preferredSize = [420, 150];

        var row = dialog.add("group");
        row.alignment = "right";
        row.spacing = 8;
        var cancel = row.add("button", undefined, cancelText, { name: "cancel" });
        var confirm = row.add("button", undefined, okText, { name: "ok" });

        var text = null;
        confirm.onClick = function () {
            text = field.text;
            dialog.close(1);
        };
        cancel.onClick = function () {
            dialog.close(0);
        };

        field.active = true;
        if (dialog.show() !== 1 || text === null) return "cancel";

        // escape() keeps the result ASCII so evalScript can carry it back.
        return "ok:" + escape(text);
    } catch (e) {
        return "cancel";
    }
}

function aiEditGetGenerationSize() {
    try {
        if (app.documents.length === 0) return "";
        var doc = app.activeDocument;
        var width = doc.width.as("px");
        var height = doc.height.as("px");
        try {
            var bounds = doc.selection.bounds;
            width = bounds[2].as("px") - bounds[0].as("px");
            height = bounds[3].as("px") - bounds[1].as("px");
        } catch (noSelection) {}
        return Math.max(1, Math.round(width)) + "x" + Math.max(1, Math.round(height));
    } catch (e) {
        return "";
    }
}

function aiEditCaptureSelection() {
    var doc = null;
    var reference = null;
    try {
        if (app.documents.length === 0) return "none";
        doc = app.activeDocument;
        aiEditShowComposite(doc);

        var bounds;
        try {
            bounds = doc.selection.bounds;
        } catch (selectionError) {
            return "none";
        }

        var token = "AI Edit Selection " + new Date().getTime();
        var channel = doc.channels.add();
        channel.name = token;
        doc.selection.store(channel);
        aiEditShowComposite(doc);

        var width = Math.max(1, Math.round(bounds[2].as("px") - bounds[0].as("px")));
        var height = Math.max(1, Math.round(bounds[3].as("px") - bounds[1].as("px")));
        var boundsText = [
            Math.round(bounds[0].as("px")),
            Math.round(bounds[1].as("px")),
            width,
            height
        ].join(",");

        // Avoid copy/copyMerged: Photoshop reports that command as unavailable
        // for some active layers and documents. A duplicate keeps the merged
        // visible pixels and can be cropped without using the clipboard.
        reference = doc.duplicate("AI Edit Reference", false);
        reference.flatten();
        // Do not call selection.clear here. Photoshop rejects that command
        // when the duplicated document resolves to a locked background layer.
        // Cropping to the captured bounds keeps the selected image usable as
        // a reference without changing pixels through the clipboard/delete UI.
        reference.crop(bounds);

        var file = new File(Folder.temp.fsName + "/ai-edit-selection-" + new Date().getTime() + ".png");
        var options = new PNGSaveOptions();
        reference.saveAs(file, options, true, Extension.LOWERCASE);
        reference.close(SaveOptions.DONOTSAVECHANGES);
        app.activeDocument = doc;
        aiEditShowComposite(doc);
        return "ok:" + token + "|" + file.fsName + "|" + boundsText;
    } catch (e) {
        try {
            if (reference) reference.close(SaveOptions.DONOTSAVECHANGES);
            if (doc) {
                app.activeDocument = doc;
                aiEditShowComposite(doc);
            }
        } catch (closeError) {}
        return "error:" + e.toString();
    }
}

function aiEditAddLayerMask(hasSelection) {
    var classicMask = new ActionDescriptor();
    classicMask.putClass(charIDToTypeID("Nw  "), charIDToTypeID("Chnl"));

    var classicTarget = new ActionReference();
    classicTarget.putEnumerated(
        charIDToTypeID("Chnl"),
        charIDToTypeID("Chnl"),
        charIDToTypeID("Msk ")
    );
    classicMask.putReference(charIDToTypeID("At  "), classicTarget);
    classicMask.putEnumerated(
        charIDToTypeID("Usng"),
        charIDToTypeID("UsrM"),
        charIDToTypeID(hasSelection ? "RvlS" : "RvlA")
    );

    try {
        executeAction(charIDToTypeID("Mk  "), classicMask, DialogModes.NO);
        return;
    } catch (classicError) {}

    var modernMask = new ActionDescriptor();
    var newChannel = new ActionReference();
    newChannel.putClass(stringIDToTypeID("channel"));
    modernMask.putReference(charIDToTypeID("null"), newChannel);

    var currentLayer = new ActionReference();
    currentLayer.putEnumerated(
        stringIDToTypeID("layer"),
        stringIDToTypeID("ordinal"),
        stringIDToTypeID("targetEnum")
    );
    modernMask.putReference(charIDToTypeID("At  "), currentLayer);
    modernMask.putEnumerated(
        stringIDToTypeID("using"),
        stringIDToTypeID("userMaskEnabled"),
        stringIDToTypeID(hasSelection ? "revealSelection" : "revealAll")
    );
    executeAction(charIDToTypeID("Mk  "), modernMask, DialogModes.NO);
}

function aiEditPlaceImage(filePath, layerName, channelName, boundsText) {
    try {
        if (app.documents.length === 0) return "error:missing-document";

        var file = new File(filePath);
        if (!file.exists) return "error:missing-file";

        var doc = app.activeDocument;
        var selectionChannel = null;
        var restoreChannel = null;
        var hadCurrentSelection = false;
        var hasSelection = false;
        if (channelName) {
            try {
                try {
                    doc.selection.bounds;
                    hadCurrentSelection = true;
                    restoreChannel = doc.channels.add();
                    restoreChannel.name = "AI Edit Restore " + new Date().getTime();
                    doc.selection.store(restoreChannel);
                } catch (noCurrentSelection) {}

                selectionChannel = doc.channels.getByName(channelName);
                doc.selection.load(selectionChannel);
                hasSelection = true;
            } catch (channelError) {}
        }

        var place = new ActionDescriptor();
        place.putPath(charIDToTypeID("null"), file);
        place.putEnumerated(charIDToTypeID("FTcs"), charIDToTypeID("QCSt"), charIDToTypeID("Qcsa"));

        var offset = new ActionDescriptor();
        offset.putUnitDouble(charIDToTypeID("Hrzn"), charIDToTypeID("#Pxl"), 0);
        offset.putUnitDouble(charIDToTypeID("Vrtc"), charIDToTypeID("#Pxl"), 0);
        place.putObject(charIDToTypeID("Ofst"), charIDToTypeID("Ofst"), offset);

        place.putBoolean(stringIDToTypeID("linked"), false);
        executeAction(charIDToTypeID("Plc "), place, DialogModes.NO);

        var placedLayer = app.activeDocument.activeLayer;
        if (layerName) placedLayer.name = layerName;

        if (boundsText) {
            var boundsValues = boundsText.split(",");
            if (boundsValues.length === 4) {
                var targetLeft = Number(boundsValues[0]);
                var targetTop = Number(boundsValues[1]);
                var targetWidth = Number(boundsValues[2]);
                var targetHeight = Number(boundsValues[3]);
                var placedBounds = placedLayer.bounds;
                var placedWidth = Math.max(1, placedBounds[2].as("px") - placedBounds[0].as("px"));
                var placedHeight = Math.max(1, placedBounds[3].as("px") - placedBounds[1].as("px"));
                placedLayer.resize(
                    targetWidth / placedWidth * 100,
                    targetHeight / placedHeight * 100,
                    AnchorPosition.TOPLEFT
                );
                placedBounds = placedLayer.bounds;
                placedLayer.translate(
                    targetLeft - placedBounds[0].as("px"),
                    targetTop - placedBounds[1].as("px")
                );
            }
        }

        app.activeDocument.activeLayer = placedLayer;
        aiEditAddLayerMask(hasSelection);

        if (selectionChannel) {
            if (hadCurrentSelection && restoreChannel) doc.selection.load(restoreChannel);
            else doc.selection.deselect();
            if (restoreChannel) restoreChannel.remove();
        }
        aiEditShowComposite(doc);

        return "ok";
    } catch (e) {
        try {
            aiEditShowComposite(app.activeDocument);
        } catch (restoreViewError) {}
        return "error:" + e.toString();
    }
}
