/**
 * generate_inner_shadow_dataset.jsx
 *
 * Purpose:
 *   Generate a systematic Inner Shadow effect dataset in Photopea.
 *   Uses stringID-based ActionDescriptor for maximum Photopea compatibility.
 *
 * Logic & Workflow:
 *   1. Creates a new RGB document with transparent background.
 *   2. Adds a normal raster layer and fills the center with black.
 *   3. Applies one Inner Shadow effect via ActionDescriptor (stringID).
 *   4. Hides the background layer.
 *   5. Saves the result as a transparent PNG.
 *
 * Usage:
 *   1. Open https://www.photopea.com in a browser.
 *   2. Alt+Ctrl+I  (or File > Automate > Script).
 *   3. Paste this script and run.
 *   4. Select output folder when prompted.
 */

var outFolder = Folder.selectDialog("Select output folder for Inner Shadow dataset");
if (!outFolder) {
    alert("No output folder selected. Exiting.");
    throw new Error("No output folder");
}

var angles       = [0, 90, 180, 270];
var distances    = [5, 15, 30];
var sizes        = [10, 30];
var chokes       = [0, 20];
var fxOpacities  = [75, 100];
var fxBlendModes = [
    { id: "normal",   name: "normal" },
    { id: "multiply", name: "multiply" }
];

function createDocument(name, width, height) {
    var doc = app.documents.add(width, height, 72, name, NewDocumentMode.RGB, DocumentFill.TRANSPARENT);
    try {
        var bg = doc.artLayers[doc.artLayers.length - 1];
        if (bg.isBackgroundLayer) {
            bg.visible = false;
        }
    } catch (e) {}
    return doc;
}

function addBlackRectangle(doc) {
    var w = doc.width;
    var h = doc.height;
    if (typeof w === "object" && typeof w.value === "function") { w = w.value; }
    if (typeof h === "object" && typeof h.value === "function") { h = h.value; }
    w = Number(w);
    h = Number(h);

    var margin = Math.round(Math.min(w, h) * 0.25);

    var layer = doc.artLayers.add();
    layer.name = "shape";

    var region = [
        [margin, margin],
        [w - margin, margin],
        [w - margin, h - margin],
        [margin, h - margin]
    ];

    doc.selection.select(region);

    var black = new SolidColor();
    black.rgb.red = 0;
    black.rgb.green = 0;
    black.rgb.blue = 0;

    doc.selection.fill(black);
    doc.selection.deselect();

    return layer;
}

/**
 * Try applying Inner Shadow via ActionDescriptor with stringID keys.
 * Returns true on success, false on failure.
 */
function applyInnerShadow(params) {
    try {
        var desc = new ActionDescriptor();

        var ref = new ActionReference();
        ref.putProperty(stringIDToTypeID("property"), stringIDToTypeID("layerEffects"));
        ref.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
        desc.putReference(stringIDToTypeID("null"), ref);

        var fxDesc = new ActionDescriptor();

        var isDesc = new ActionDescriptor();
        isDesc.putBoolean(stringIDToTypeID("enabled"), true);

        // Blend mode
        isDesc.putEnumerated(stringIDToTypeID("mode"), stringIDToTypeID("blendMode"), stringIDToTypeID(params.blendMode));

        // Color
        var colorDesc = new ActionDescriptor();
        colorDesc.putDouble(stringIDToTypeID("red"), 0);
        colorDesc.putDouble(stringIDToTypeID("grain"), 0);
        colorDesc.putDouble(stringIDToTypeID("blue"), 0);
        isDesc.putObject(stringIDToTypeID("color"), stringIDToTypeID("RGBColor"), colorDesc);

        // Opacity (0-100)
        isDesc.putInteger(stringIDToTypeID("opacity"), params.opacity);

        // Angle
        isDesc.putInteger(stringIDToTypeID("localLightingAngle"), params.angle);

        // Global light
        isDesc.putBoolean(stringIDToTypeID("useGlobalAngle"), false);

        // Distance
        isDesc.putInteger(stringIDToTypeID("distance"), params.distance);

        // Choke
        isDesc.putInteger(stringIDToTypeID("chokeMatte"), params.choke);

        // Blur (size)
        isDesc.putInteger(stringIDToTypeID("blur"), params.size);

        // Noise
        isDesc.putInteger(stringIDToTypeID("noise"), 0);

        // Anti-alias
        isDesc.putBoolean(stringIDToTypeID("antiAlias"), true);

        // Transfer function (contour)
        isDesc.putBoolean(stringIDToTypeID("transferSpecIsScaled"), false);

        fxDesc.putObject(stringIDToTypeID("innerShadow"), stringIDToTypeID("innerShadow"), isDesc);

        desc.putObject(stringIDToTypeID("to"), stringIDToTypeID("layerEffects"), fxDesc);

        executeAction(stringIDToTypeID("set"), desc, DialogModes.NO);
        return true;
    } catch (e) {
        $.writeln("stringID failed: " + e);
        return false;
    }
}

/**
 * Fallback: apply via charID-based descriptor.
 */
function applyInnerShadowCharID(params) {
    try {
        var desc = new ActionDescriptor();

        var ref = new ActionReference();
        ref.putProperty(charIDToTypeID("Prpr"), charIDToTypeID("Lefx"));
        ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
        desc.putReference(charIDToTypeID("null"), ref);

        var fxDesc = new ActionDescriptor();

        var isDesc = new ActionDescriptor();
        isDesc.putBoolean(charIDToTypeID("enab"), true);
        isDesc.putBoolean(charIDToTypeID("present"), true);
        isDesc.putBoolean(charIDToTypeID("showInDialog"), true);

        // Blend mode
        isDesc.putEnumerated(charIDToTypeID("Blnd"), charIDToTypeID("BlnM"), charIDToTypeID(params.blendModeCharID));

        // Color
        var clr = new ActionDescriptor();
        clr.putDouble(charIDToTypeID("Rd  "), 0);
        clr.putDouble(charIDToTypeID("Grn "), 0);
        clr.putDouble(charIDToTypeID("Bl  "), 0);
        isDesc.putObject(charIDToTypeID("Clr "), charIDToTypeID("RGBC"), clr);

        // Opacity: 0-100 integer
        isDesc.putInteger(charIDToTypeID("Opct"), params.opacity);

        // Angle: use lagl (local angle) instead of Angl
        isDesc.putInteger(charIDToTypeID("lagl"), params.angle);
        isDesc.putBoolean(charIDToTypeID("uglg"), false);

        // Distance
        isDesc.putInteger(charIDToTypeID("Dstn"), params.distance);

        // Choke
        isDesc.putInteger(charIDToTypeID("Ckmt"), params.choke);

        // Blur
        isDesc.putInteger(charIDToTypeID("blur"), params.size);

        // Noise
        isDesc.putInteger(charIDToTypeID("Nose"), 0);

        fxDesc.putObject(charIDToTypeID("IrSh"), charIDToTypeID("IrSh"), isDesc);

        desc.putObject(charIDToTypeID("T   "), charIDToTypeID("Lefx"), fxDesc);

        executeAction(charIDToTypeID("setd"), desc, DialogModes.NO);
        return true;
    } catch (e) {
        $.writeln("charID failed: " + e);
        return false;
    }
}

function saveTransparentPng(doc, file) {
    var pngOptions = new PNGSaveOptions();
    pngOptions.compression = 6;
    pngOptions.interlaced = false;
    doc.saveAs(file, pngOptions, true, Extension.LOWERCASE);
}

var count = 0;
var errors = 0;
var total = angles.length * distances.length * sizes.length * chokes.length * fxOpacities.length * fxBlendModes.length;

var blendModeCharMap = {
    "normal":   "Nrml",
    "multiply": "Mltp"
};

for (var a = 0; a < angles.length; a++) {
    for (var d = 0; d < distances.length; d++) {
        for (var s = 0; s < sizes.length; s++) {
            for (var c = 0; c < chokes.length; c++) {
                for (var o = 0; o < fxOpacities.length; o++) {
                    for (var b = 0; b < fxBlendModes.length; b++) {
                        var bmName = fxBlendModes[b].name;
                        var bmID   = fxBlendModes[b].id;

                        var fileName = "is_" +
                            "a" + angles[a] + "_" +
                            "d" + distances[d] + "_" +
                            "s" + sizes[s] + "_" +
                            "c" + chokes[c] + "_" +
                            "op" + fxOpacities[o] + "_" +
                            "bm" + bmName +
                            ".png";

                        var file = new File(outFolder + "/" + fileName);
                        if (file.exists) {
                            count++;
                            continue;
                        }

                        var params = {
                            angle:        angles[a],
                            distance:     distances[d],
                            size:         sizes[s],
                            choke:        chokes[c],
                            opacity:      fxOpacities[o],
                            blendMode:    bmID,
                            blendModeCharID: blendModeCharMap[bmID] || "Nrml"
                        };

                        var doc = createDocument("inner_shadow_test", 256, 256);
                        var layer = addBlackRectangle(doc);

                        var applied = applyInnerShadow(params);
                        if (!applied) {
                            applied = applyInnerShadowCharID(params);
                        }

                        if (applied) {
                            try {
                                saveTransparentPng(doc, file);
                            } catch (e2) {
                                $.writeln("Save error: " + fileName + " — " + e2);
                                errors++;
                            }
                        } else {
                            $.writeln("FAILED to apply effect: " + fileName);
                            errors++;
                        }

                        doc.close(SaveOptions.DONOTSAVECHANGES);

                        count++;
                        if (count % 10 === 0) {
                            $.writeln("Progress: " + count + " / " + total + " (errors: " + errors + ")");
                        }
                    }
                }
            }
        }
    }
}

alert("Done. Generated " + count + " images, " + errors + " errors.\n" + outFolder.fsName);
