/*
 * EAN13.jsx - EAN-13 Barcode Generator for Adobe Illustrator
 *
 * Converts 13-digit text frames into production-ready EAN-13 barcodes
 * with ISO/IEC 15420 compliance, print compensation (BWR), and customizable height.
 *
 * Author: Katja Bjerrum
 * Website: https://productivista.com
 * Email: katja@productivista.com
 *
 * License: Free for commercial and personal use
 *
 * Attribution: If you use this code or portions of it in your own projects,
 * please credit the original author:
 * "Based on EAN13.jsx by Katja Bjerrum (https://productivista.com)"
 *
 * Features:
 * - ISO/IEC 15420 compliant barcode dimensions
 * - Bar Width Reduction (BWR) for print compensation
 * - Proportional or fixed barcode height modes
 * - Optional linked EPS file export
 * - Persistent user preferences
 *
 * Usage:
 * - First run: Settings dialog appears automatically
 * - Subsequent runs: Uses saved preferences
 * - To change settings: Hold Option (Mac) or Alt (Windows) while running the script
 *
 * Requirements:
 * - Adobe Illustrator CS6 or later
 * - OCR-B Standard font installed
 *
 * Version: 2.1 (2025)
 */

// Check if a document is open
if (!app.documents.length) {
    alert("Please open a document with 13-digit text frames before running this script.");
} else {

var doc = app.activeDocument;

app.coordinateSystem = CoordinateSystem.DOCUMENTCOORDINATESYSTEM;

var fontName = "OCRBStd";

// Get settings from user preference or show dialog if Option/Alt key is held
var settings = getBWRSettings();

if (settings !== null) {
    // User did not cancel - proceed with barcode generation

    var BWR_PERCENT = settings.bwr;
    var USE_LINKED_FILES = settings.link;
    var REMOVE_WHITE_RECT = settings.removeWhite;
    var BARCODE_HEIGHT_MODE = settings.heightMode; // "proportional" or "fixed"
    var BARCODE_HEIGHT_VALUE = settings.heightValue; // divisor or fixed height
    var ADD_QUIET_ZONE_INDICATOR = settings.quietZoneIndicator; // Add ">" at end
    var WHITE_RECT_MARGIN_TOP = settings.marginTop; // Top margin in document units
    var WHITE_RECT_MARGIN_LEFT = settings.marginLeft; // Left margin in document units
    var WHITE_RECT_MARGIN_RIGHT = settings.marginRight; // Right margin in document units
    var WHITE_RECT_MARGIN_BOTTOM = settings.marginBottom; // Bottom margin in document units
    var WIDTH_MODE = settings.widthMode; // "auto" or "manual"
    var MANUAL_MAGNIFICATION = settings.manualMagnification || 100; // Manual magnification (80-200)
    var ENFORCE_MIN_HEIGHT = settings.enforceMinHeight; // Enforce GS1 minimum height
    var OUTLINE_TEXT = settings.outlineText; // Convert text to outlines (print-ready)
    var ADD_QUIET_ZONE_GUIDES = settings.addQuietZoneGuides; // Add quiet zone guides
    var validationResults = []; // Track validation results

    var barcodeTextArr = [];


    if (fontAvailable(fontName)) {

    // Determine which text frames to process: selection or all
    var textFramesToProcess = [];
    var selection = doc.selection;

    // Check if there are selected text frames
    if (selection && selection.length > 0) {
        for (var i = 0; i < selection.length; i++) {
            if (selection[i].typename === "TextFrame") {
                textFramesToProcess.push(selection[i]);
            }
        }
    }

    // If no text frames in selection, process all text frames in document
    if (textFramesToProcess.length === 0) {
        for (var i = 0; i < doc.textFrames.length; i++) {
            textFramesToProcess.push(doc.textFrames[i]);
        }
    }

    //Select 13 digits and replace it with a barcode
    for (var i = 0; i < textFramesToProcess.length; i++) {

        if (textFramesToProcess[i].contents.match(/^\d{13}$/gi)) {

            var textParent = textFramesToProcess[i].parent;

            //If layer with text is locked or invisible the script will ignore them
            if (textParent.locked == true || textParent.visible == false) {

                continue;
            };

            if (textFramesToProcess[i].kind == TextType.AREATEXT) {

                areaTextJust(textFramesToProcess[i]);

                textFramesToProcess[i].convertAreaObjectToPointObject();


            }

            var myCode = textFramesToProcess[i].contents;
            
            if (CheckDigit(myCode) != myCode[12]) {

                alert("The barcode " + myCode + " does not have the right checksum. The checksum digit must be " + CheckDigit(myCode));


            }
            else barcodeTextArr.push(textFramesToProcess[i]);
        }
    };

    // Alert if no valid 13-digit codes found
    if (barcodeTextArr.length === 0) {
        alert("No valid 13-digit EAN codes found.\n\nMake sure text frames contain exactly 13 digits.\n\nTip: Select specific text frames to process only those, or run without selection to process all text in the document.");
    } else {

    // Check if document is saved and get barcode folder (if using linked files)
    var barcodeFolder = null;
    if (USE_LINKED_FILES) {
        if (checkDocumentSaved()) {
            barcodeFolder = getBarcodeFolder();
            if (barcodeFolder === null) {
                // Folder creation failed, fall back to embedded
                USE_LINKED_FILES = false;
            }
        } else {
            // User chose embedded or cancelled
            USE_LINKED_FILES = false;
        }
    }

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

        var barcodeText = barcodeTextArr[i];
        var EANtext = barcodeText.contents; // Get EAN text for validation

        var textParent = barcodeText.parent;

        var EANGroup = textParent.groupItems.add();

        // Determine width and magnification based on width mode
        var Width;
        var magnification;
        var validation = null;

        if (WIDTH_MODE === "manual") {
            // Manual mode: use selected magnification
            magnification = MANUAL_MAGNIFICATION;
            var barsWidthMM = 31.35 * (magnification / 100);
            Width = mmToPoints(barsWidthMM);

            var leftQZ_MM = 3.63 * (magnification / 100);
            var rightQZ_MM = 2.31 * (magnification / 100);
            var totalWidthMM = barsWidthMM + leftQZ_MM + rightQZ_MM;

            validation = {
                mode: "manual",
                magnification: magnification,
                barsWidthMM: barsWidthMM,
                totalWidthMM: totalWidthMM,
                ean: EANtext
            };
        } else {
            // Auto-detect mode: calculate from text frame width
            var textWidthMM = pointsToMM(barcodeText.width);
            magnification = (textWidthMM / 37.29) * 100;

            // Clamp to GS1 valid range
            var originalMagnification = magnification;
            if (magnification < 80) magnification = 80;
            if (magnification > 200) magnification = 200;

            var barsWidthMM = 31.35 * (magnification / 100);
            Width = mmToPoints(barsWidthMM);

            var leftQZ_MM = 3.63 * (magnification / 100);
            var rightQZ_MM = 2.31 * (magnification / 100);
            var totalWidthMM = barsWidthMM + leftQZ_MM + rightQZ_MM;

            validation = {
                mode: "auto",
                requestedWidthMM: textWidthMM,
                magnification: magnification,
                clamped: (magnification !== originalMagnification),
                barsWidthMM: barsWidthMM,
                totalWidthMM: totalWidthMM,
                ean: EANtext
            };
        }

        validationResults.push(validation);

        justText(barcodeText);

        var matrix = barcodeText.matrix;

        var Angle = -180 / Math.PI * Math.atan2(matrix.mValueC, matrix.mValueD)

        // Calculate height
        var userHeight;
        if (BARCODE_HEIGHT_MODE === "proportional") {
            // Proportional mode: Height = Width / divisor
            userHeight = Width / BARCODE_HEIGHT_VALUE;
        } else {
            // Fixed mode: Height = fixed value in document units
            userHeight = BARCODE_HEIGHT_VALUE;
        }

        // Apply GS1 minimum height enforcement if enabled
        var Height;
        if (ENFORCE_MIN_HEIGHT) {
            // Calculate GS1 minimum height for current magnification
            var GS1_MIN_HEIGHT_MM = 22.85 * (magnification / 100);
            var GS1_MIN_HEIGHT_POINTS = mmToPoints(GS1_MIN_HEIGHT_MM);
            // Use the greater of user's height or GS1 minimum
            Height = Math.max(userHeight, GS1_MIN_HEIGHT_POINTS);
        } else {
            // Use user's exact height
            Height = userHeight;
        }

        // Position barcode so the first digit aligns with original text anchor
        // First digit is at local x = -block * 8, bounding box left is at -block * 11 - margin
        // Offset from bounding box to first digit = block * 3 + margin
        var block = Width / 95;
        var newX = barcodeText.anchor[0] - (block * 3 + WHITE_RECT_MARGIN_LEFT);

        // Align barcode text baseline with original text baseline
        // Barcode digits are at local Y = -Height * 1.03 from group origin
        // Add half the barcode font size to move group up
        var newY = barcodeText.anchor[1] + WHITE_RECT_MARGIN_TOP + Height * 1.03 + block * 5;

        var roto_X = barcodeText.anchor[0];

        var roto_Y = barcodeText.anchor[1];

        var delta = Width / 20;

        var barColor = make_cmyk(0, 0, 0, 100);

        var barcode = CreateBarcode(Width, Height, EANtext, EANGroup, barColor, barcodeText, WHITE_RECT_MARGIN_TOP, WHITE_RECT_MARGIN_LEFT, WHITE_RECT_MARGIN_RIGHT, WHITE_RECT_MARGIN_BOTTOM);

        // Find white rectangle for removal
        var whiteRect = null;
        for (var j = barcode.pathItems.length - 1; j >= 0; j--) {
            var item = barcode.pathItems[j];
            if (item.filled && !item.stroked) {
                var fill = item.fillColor;
                // Check if it's white (all CMYK values are 0)
                if (fill.cyan === 0 && fill.magenta === 0 && fill.yellow === 0 && fill.black === 0) {
                    whiteRect = item;
                    break;
                }
            }
        }

        if (REMOVE_WHITE_RECT && whiteRect !== null) {
            whiteRect.remove();
        }

        // Convert barcode text to outlines if option is enabled
        if (OUTLINE_TEXT) {
            // Collect all textFrames in the barcode group
            var textFrames = [];
            for (var j = barcode.textFrames.length - 1; j >= 0; j--) {
                textFrames.push(barcode.textFrames[j]);
            }

            // Convert each textFrame to outlines
            for (var j = 0; j < textFrames.length; j++) {
                try {
                    textFrames[j].createOutline();
                } catch (e) {
                    // Silently continue if font not installed or already outlined
                }
            }
        }

        // Now handle linked files if enabled
        if (USE_LINKED_FILES && barcodeFolder !== null) {
            // For linked files: export barcode WITHOUT guide, then create guide in document

            // Export barcode as EPS (before adding guide)
            var epsFile = exportBarcodeAsEPS(barcode, EANtext, barcodeFolder);

            if (epsFile !== null) {
                // Place linked file at same position and rotation as embedded would be
                var placedItem = placeBarcodeLink(epsFile, [newX, newY], Angle, roto_X, roto_Y);

                // Create quiet zone guide if option is enabled
                if (ADD_QUIET_ZONE_GUIDES) {
                    var block = Width / 95;
                    var blockHeightExtra = Height * 1.07;
                    var guideWidth = block * 113;
                    var guideHeight = blockHeightExtra * 1.16;

                    // Account for white rectangle margins in guide positioning
                    // The placed item's bounding box includes white rect margins
                    // Guide should be offset by margins from the placed item's top-left
                    var guideLeft = newX + WHITE_RECT_MARGIN_LEFT;
                    var guideTop = newY - WHITE_RECT_MARGIN_TOP;

                    // Create guide in document space
                    var guideRect = textParent.pathItems.rectangle(guideTop, guideLeft, guideWidth, guideHeight);
                    guideRect.guides = true;
                    guideRect.filled = false;
                    guideRect.stroked = false;

                    // Rotate guide around same point as barcode
                    if (Angle !== 0) {
                        rotate_around_point(guideRect, roto_Y, roto_X, Angle);
                    }

                    // Group guide with placed item
                    var linkGroup = textParent.groupItems.add();
                    placedItem.moveToBeginning(linkGroup);
                    guideRect.moveToBeginning(linkGroup);
                }

                // Remove the embedded barcode
                barcode.remove();
            }
            // If export fails, keep the embedded barcode (already positioned)
        } else {
            // For embedded barcodes: create guide in barcode group before positioning
            if (ADD_QUIET_ZONE_GUIDES) {
                var block = Width / 95;
                var blockHeightExtra = Height * 1.07;
                var guideWidth = block * 113;
                var guideHeight = blockHeightExtra * 1.16;

                // Create guide in barcode's local coordinates
                var guideRect = barcode.pathItems.rectangle(0, -block * 11, guideWidth, guideHeight);
                guideRect.guides = true;
                guideRect.filled = false;
                guideRect.stroked = false;
            }

            // Position and rotate barcode (with guide)
            barcode.position = [newX, newY];
            rotate_around_point(barcode, roto_Y, roto_X, Angle);
        }

    }

    } // End else - valid barcodes found

}

else {

    alert("Looks, like the " + fontName + " font is not available in you computer. Please install " + fontName + " font and try again.");

};

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

        barcodeTextArr[i].remove();

    };

} // End if (settings !== null)

} // End if (app.documents.length)


////////////////          FUNCTIONS        /////////////////////////////////////////////////////////////////


// Unit Conversion Utilities
function pointsToMM(points) {
    return points * 0.352778;
}

function mmToPoints(mm) {
    return mm / 0.352778;
}

// Convert points to document ruler units
function pointsToDocUnits(points) {
    var doc = app.activeDocument;
    var rulerUnits = doc.rulerUnits;

    // RulerUnits enum values
    if (rulerUnits == RulerUnits.Millimeters) {
        return points * 0.352778;
    } else if (rulerUnits == RulerUnits.Centimeters) {
        return points * 0.0352778;
    } else if (rulerUnits == RulerUnits.Inches) {
        return points / 72;
    } else if (rulerUnits == RulerUnits.Picas) {
        return points / 12;
    } else if (rulerUnits == RulerUnits.Points) {
        return points;
    } else {
        // Default to points for other units
        return points;
    }
}

// Convert document ruler units to points
function docUnitsToPoints(value) {
    var doc = app.activeDocument;
    var rulerUnits = doc.rulerUnits;

    if (rulerUnits == RulerUnits.Millimeters) {
        return value / 0.352778;
    } else if (rulerUnits == RulerUnits.Centimeters) {
        return value / 0.0352778;
    } else if (rulerUnits == RulerUnits.Inches) {
        return value * 72;
    } else if (rulerUnits == RulerUnits.Picas) {
        return value * 12;
    } else if (rulerUnits == RulerUnits.Points) {
        return value;
    } else {
        return value;
    }
}

// Get unit name for display
function getDocUnitName() {
    var doc = app.activeDocument;
    var rulerUnits = doc.rulerUnits;

    if (rulerUnits == RulerUnits.Millimeters) return "mm";
    if (rulerUnits == RulerUnits.Centimeters) return "cm";
    if (rulerUnits == RulerUnits.Inches) return "in";
    if (rulerUnits == RulerUnits.Picas) return "pc";
    if (rulerUnits == RulerUnits.Points) return "pt";
    return "units";
}

function validateGS1Width(widthInMM) {
    // GS1 bars width (excluding quiet zones) - from GS1 Australia fact sheet
    var GS1_NOMINAL = 31.35;  // 100% bars width (37.29mm total)
    var GS1_MIN = 25.08;      // 80% bars width (29.83mm total)
    var GS1_MAX = 62.70;      // 200% bars width (74.58mm total)

    var isCompliant = (widthInMM >= GS1_MIN && widthInMM <= GS1_MAX);
    var percentOfNominal = Math.round((widthInMM / GS1_NOMINAL) * 100);

    var status;
    if (Math.abs(widthInMM - GS1_NOMINAL) < 0.1) {
        status = "nominal";
    } else if (isCompliant) {
        status = "compliant";
    } else {
        status = "non-compliant";
    }

    return {
        isCompliant: isCompliant,
        status: status,
        widthMM: widthInMM,
        percentOfNominal: percentOfNominal,
        nominalMM: GS1_NOMINAL,
        minMM: GS1_MIN,
        maxMM: GS1_MAX
    };
}


function bcRenderChar(x, y, w, h, col, gr, gapD, bwr) {

    this.x = x;

    this.y = y;

    this.w = w;

    this.h = h;

    this.col = col;

    this.gr = gr;

    this.gapD = gapD;

    this.bwr = bwr; // Bar Width Reduction percentage



    this.L = {
        "0": [3, 2, 6, 1],
        "1": [2, 2, 6, 1],
        "2": [2, 1, 5, 2],
        "3": [1, 4, 6, 1],
        "4": [1, 1, 5, 2],
        "5": [1, 2, 6, 1],
        "6": [1, 1, 3, 4],
        "7": [1, 3, 5, 2],
        "8": [1, 2, 4, 3],
        "9": [3, 1, 5, 2]
    }

    this.G = {
        "0": [1, 1, 4, 3],
        "1": [1, 2, 5, 2],
        "2": [2, 2, 5, 2],
        "3": [1, 1, 6, 1],
        "4": [2, 3, 6, 1],
        "5": [1, 3, 6, 1],
        "6": [4, 1, 6, 1],
        "7": [2, 1, 6, 1],
        "8": [3, 1, 6, 1],
        "9": [2, 1, 4, 3]
    }

    this.dictL = {
        "0": "LLLLLL",
        "1": "LLGLGG",
        "2": "LLGGLG",
        "3": "LLGGGL",
        "4": "LGLLGG",
        "5": "LGGLLG",
        "6": "LGGGLL",
        "7": "LGLGLG",
        "8": "LGLGGL",
        "9": "LGGLGL"
    }

    this.dictR = {
        "sep": [1, 1, 3, 1],
        "0": [0, 3, 5, 1],
        "1": [0, 2, 4, 2],
        "2": [0, 2, 3, 2],
        "3": [0, 1, 5, 1],
        "4": [0, 1, 2, 3],
        "5": [0, 1, 3, 3],
        "6": [0, 1, 2, 1],
        "7": [0, 1, 4, 1],
        "8": [0, 1, 3, 1],
        "9": [0, 3, 4, 1]
    }

    this.drawLeft = function (content) {

        var mySeq = this.dictL[content[0]];

        for (var i = 1; i < content.length; i++) {

            var myLG = mySeq[i - 1];


            if (myLG == "L") {

                var parameters = this.L[content[i]];


            }

            else if (myLG == "G") {

                var parameters = this.G[content[i]];


            }

            // Apply Bar Width Reduction (BWR) to compensate for ink spread
            var reduction = this.w * this.bwr / 200; // Half reduction on each side

            rect = this.gr.pathItems.rectangle(this.y, this.x + this.w * parameters[0] + reduction, this.w * parameters[1] - 2 * reduction, this.h);

            rect.stroked = false;

            rect.filled = true;

            rect.fillColor = this.col;

            rect = this.gr.pathItems.rectangle(this.y, this.x + this.w * parameters[2] + reduction, this.w * parameters[3] - 2 * reduction, this.h);

            rect.stroked = false;

            rect.filled = true;

            rect.fillColor = this.col;

            this.x = this.x += this.gapD;

        }

    }

    this.draw = function (textChar) {

        var parameters = this.dictR[textChar];

        // Apply Bar Width Reduction (BWR) to compensate for ink spread
        var reduction = this.w * this.bwr / 200; // Half reduction on each side

        rect = this.gr.pathItems.rectangle(this.y, this.x + this.w * parameters[0] + reduction, this.w * parameters[1] - 2 * reduction, this.h);

        rect.stroked = false;

        rect.filled = true;

        rect.fillColor = this.col;

        rect = this.gr.pathItems.rectangle(this.y, this.x + this.w * parameters[2] + reduction, this.w * parameters[3] - 2 * reduction, this.h);

        rect.stroked = false;

        rect.filled = true;

        rect.fillColor = this.col;

        //this.x = this.x += this.gapD;

        

    }
};


function fontAvailable(myName) {

    //Function checks if font exists

    var myFont = true;

    try {

        var myFont = textFonts.getByName(myName);

    }

    catch (e) {

        var myFont = false;

    }

    return myFont;
};


function make_cmyk(c, m, y, k) {

    var colorRef = new CMYKColor();

    colorRef.cyan = c;

    colorRef.magenta = m;

    colorRef.yellow = y;

    colorRef.black = k;

    return colorRef;
};


function CreateBarcode(Width, Height, BarcodeNr, Group, barColor, textArtRange, marginTop, marginLeft, marginRight, marginBottom) {
    // Function creates barcode

    //adjust width for correct fit per ISO/IEC 15420
    //EAN-13 bars = 95 modules, quiet zones = 11 left + 7 right (added separately)
    var block = Width / 95;

    var blockHeightExtra = Height * 1.07; //heigtht of barcode

    var fontSize = block * 10;//font size

    var zX = 0;

    var zY = 0;

    var BarcodeNr = textArtRange.contents;

    var gapD = block * 7;

    // Create white rectangle with margins
    var whiteRect = Group.pathItems.rectangle(
        zY + marginTop,                                    // top
        zX - block * 11 - marginLeft,                      // left (11 modules for left quiet zone per ISO)
        block * 113 + marginLeft + marginRight,            // width (add left and right margins)
        blockHeightExtra * 1.16 + marginTop + marginBottom // height
    );

    whiteRect.stroked = false;

    whiteRect.filled = true;

    whiteRect.fillColor = make_cmyk(0, 0, 0, 0);

    var bcRenderObject = new bcRenderChar(zX, zY, block, blockHeightExtra, barColor, Group, gapD, BWR_PERCENT);

    bcRenderObject.draw("sep");

    bcRenderObject.x += block * 4;

    bcRenderObject.h = Height;

    bcRenderObject.drawLeft(BarcodeNr.substring(0, 7));

    //bcRenderObject.x += block;

    bcRenderObject.h = blockHeightExtra;

     bcRenderObject.draw("sep");

     bcRenderObject.x += block * 5;

     bcRenderObject.h = Height;

     for (var j = 7; j < 12; j++) {
         

         bcRenderObject.draw(BarcodeNr[j]);

         bcRenderObject.x += gapD;

     }
     bcRenderObject.draw(BarcodeNr[12]);
     
     bcRenderObject.x += block * 6;
     bcRenderObject.h = blockHeightExtra;

     bcRenderObject.draw("sep");

    var topPos = - Height * 1.03;

    pointText(Group, fontSize, BarcodeNr.charAt(0), topPos, block - block * 9, fontName, 1);

    pointText(Group, fontSize, BarcodeNr.substring(1, 7), topPos, block + block * 3, fontName, 1);

    pointText(Group, fontSize, BarcodeNr.substring(7, 13), topPos, block + block * 49, fontName, 1);

    // Add quiet zone indicator ">" if enabled
    if (ADD_QUIET_ZONE_INDICATOR) {
        // Position ">" in the right quiet zone
        // Bars end at module 95, right quiet zone is 7 modules (95-102)
        // Position at module 96.5 to account for character width
        pointText(Group, fontSize, ">", topPos, block * 96.5, fontName, 1);
    }

    return Group;
};


function pointText(Group, fontSize, charNr, topPos, leftPos, fontName, fontScale) {

    //Creates barcode text

    var pointTextRef = Group.textFrames.add();

    pointTextRef.textRange.size = fontSize;

    pointTextRef.contents = charNr;

    pointTextRef.position = [leftPos, topPos];

    pointTextRef.textRange.characterAttributes.textFont = textFonts.getByName(fontName);

    pointTextRef.textRange.characterAttributes.size = pointTextRef.textRange.characterAttributes.size * fontScale;

    return pointTextRef;

};


function CheckDigit(myCode) {
    //Calculate checksum of a 13 digit number and compare to last digit
    //Number must be 13 digit long, or the calculation will be wrong
    var mySum = 0;
    for (var j = 0; j < myCode.length - 1; j = j + 1) {
        //Determine weight to multiply to current digit
        if (j % 2 == 0) {
            var weight = 1
        }
        else {
            var weight = 3
        }

        var myNumber = myCode[j] * weight;
        mySum = mySum + myNumber;


    }

    checkDigit = Math.ceil(mySum / 10) * 10 - mySum

    return checkDigit;
};


function rotate_around_point(obj, roto_X, roto_Y, angle) {
    // Utility that rotates an object around an arbitrary point

    //Translate from point to document origin 
    obj.translate(-roto_Y, -roto_X);

    //Rotate around document origin
    obj.rotate(angle, true, true, true, true, Transformation.DOCUMENTORIGIN);

    //Translate back
    obj.translate(roto_Y, roto_X);
};


function justText(mySelection) {

    //Function justificates text without moving it around

    var locArr = new Array();

    var myJust = Justification.FULLJUSTIFYLASTLINELEFT;

    locArr = addtoList(mySelection, locArr);


    for (all in locArr) {

        locArr[all][0].story.textRange.justification = myJust;

        locArr[all][0].top = locArr[all][1];

        locArr[all][0].left = locArr[all][2];
    };

    function addtoList(obj, myArray) {

        var temp = new Array();

        temp[0] = obj;

        temp[1] = obj.top;

        temp[2] = obj.left;

        myArray.push(temp);

        return myArray;
    }

};

function areaTextJust(myText) {

    //If FULLJUSTIFYLASTLINE changes to Justification
    //NB:Justification.LEFT doesn´t work

    if (myText.kind == TextType.AREATEXT) {

        if (myText.paragraphs[0].paragraphAttributes.justification == Justification.FULLJUSTIFYLASTLINECENTER) {

            myText.paragraphs[0].paragraphAttributes.justification = Justification.CENTER;

        }
        else if (myText.paragraphs[0].paragraphAttributes.justification == Justification.FULLJUSTIFYLASTLINERIGHT) {

            myText.paragraphs[0].paragraphAttributes.justification = Justification.RIGHT;

        }
        else if (myText.paragraphs[0].paragraphAttributes.justification == Justification.FULLJUSTIFYLASTLINELEFT) {

            myText.paragraphs[0].paragraphAttributes.justification = Justification.LEFT;

        }

    }
};


////////////////          BWR SETTINGS FUNCTIONS        /////////////////////////////////////////////////////////////////


function getBWRSettings() {
    var prefFile = new File(getPreferencePath());
    var isFirstRun = !prefFile.exists;

    // First run: show dialog automatically if no preference file exists
    if (isFirstRun) {
        var settings = showBWRDialog();
        if (settings !== null) {
            // Show first-run confirmation message
            alert("Settings saved!\n\nThe settings dialog will not appear automatically next time.\n\nTo change settings:\nHold Option (Mac) or Alt (Windows) while running the script.\n\nLearn more:\nproductivista.com/ean-13-in-adobe-illustrator");
        }
        return settings;
    }

    // Subsequent runs: show dialog only if modifier key (Option/Alt) is held
    if (isModifierKeyPressed()) {
        return showBWRDialog();
    }

    // Otherwise use saved preference
    return readBWRPreference();
}


function isModifierKeyPressed() {
    // Check if Alt/Option key is held when script starts
    try {
        return ScriptUI.environment.keyboardState.altKey;
    } catch (e) {
        return false;
    }
}


function getPreferencePath() {
    // Cross-platform preference file path
    // Mac: ~/Library/Preferences/EAN13_BWR.txt
    // Windows: %APPDATA%/EAN13_BWR.txt
    return Folder.userData + "/EAN13_BWR.txt";
}


function readBWRPreference() {
    var prefFile = new File(getPreferencePath());
    var defaults = {
        bwr: 0,
        link: false,
        removeWhite: false,
        heightMode: "proportional",  // Default to proportional mode
        heightValue: 1.63,           // Default divisor = 1.63 (GS1-compliant: height = 22.85mm at 100%)
        quietZoneIndicator: false,   // Add ">" quiet zone indicator
        marginTop: 0,                // Top margin in document units (points)
        marginLeft: 0,               // Left margin in document units (points)
        marginRight: 0,              // Right margin in document units (points)
        marginBottom: 0,             // Bottom margin in document units (points)
        widthMode: "auto",           // Width mode: "auto" or "manual"
        manualMagnification: 100,    // Manual magnification percentage (80-200)
        enforceMinHeight: true,      // Enforce GS1 minimum height
        outlineText: false,          // Convert text to outlines (print-ready)
        addQuietZoneGuides: false    // Add quiet zone guides
    };

    if (prefFile.exists) {
        try {
            prefFile.open('r');
            var line1 = prefFile.readln();
            var line2 = prefFile.readln();
            var line3 = prefFile.readln();
            var line4 = prefFile.readln();
            var line5 = prefFile.readln();
            var line6 = prefFile.readln();
            var line7 = prefFile.readln();
            var line8 = prefFile.readln();
            var line9 = prefFile.readln();
            var line10 = prefFile.readln();
            var line11 = prefFile.readln();
            var line12 = prefFile.readln();
            var line13 = prefFile.readln();
            var line14 = prefFile.readln();
            var line15 = prefFile.readln();
            prefFile.close();

            var bwr = parseInt(line1);
            var link = (line2 === "true");
            var removeWhite = (line3 === "true");
            var heightMode = line4.toString();
            var heightValue = parseFloat(line5);
            var quietZoneIndicator = (line6 === "true");
            var marginTop = parseFloat(line7);
            var marginLeft = parseFloat(line8);
            var marginRight = parseFloat(line9);
            var marginBottom = parseFloat(line10);

            // Line 11: widthMode (with backward compatibility for old useGS1Width)
            var widthMode = "auto";
            if (line11 === "auto" || line11 === "manual") {
                widthMode = line11; // New format
            } else if (line11 === "true" || line11 === "false") {
                widthMode = "auto"; // Old format: always default to auto
            }

            // Line 12: manualMagnification
            var manualMagnification = parseFloat(line12) || 100;

            // Line 13: enforceMinHeight (with backward compatibility)
            var enforceMinHeight = true; // Default to enforcing
            if (line13 === "true" || line13 === "false") {
                enforceMinHeight = (line13 === "true");
            }

            // Line 14: outlineText (with backward compatibility)
            var outlineText = false; // Default to off
            if (line14 === "true" || line14 === "false") {
                outlineText = (line14 === "true");
            }

            // Line 15: addQuietZoneGuides (with backward compatibility)
            var addQuietZoneGuides = false; // Default to off
            if (line15 === "true" || line15 === "false") {
                addQuietZoneGuides = (line15 === "true");
            }

            // Validate mode
            if (heightMode !== "proportional" && heightMode !== "fixed") {
                heightMode = "proportional";
            }

            // Default to 3.3 if parsing fails
            if (isNaN(heightValue)) {
                heightValue = 3.3;
            }

            // Validate margin values (default to 3 if invalid)
            if (isNaN(marginTop) || marginTop < 0) marginTop = 3;
            if (isNaN(marginLeft) || marginLeft < 0) marginLeft = 3;
            if (isNaN(marginRight) || marginRight < 0) marginRight = 3;
            if (isNaN(marginBottom) || marginBottom < 0) marginBottom = 3;

            // Validate widthMode
            if (widthMode !== "auto" && widthMode !== "manual") {
                widthMode = "auto";
            }

            // Validate manualMagnification value (80-200%, default to 100 if invalid)
            if (isNaN(manualMagnification) || manualMagnification < 80 || manualMagnification > 200) {
                manualMagnification = 100;
            }

            // Validate BWR value is reasonable (0-20%)
            if (!isNaN(bwr) && bwr >= 0 && bwr <= 20) {
                return {bwr: bwr, link: link, removeWhite: removeWhite, heightMode: heightMode, heightValue: heightValue, quietZoneIndicator: quietZoneIndicator, marginTop: marginTop, marginLeft: marginLeft, marginRight: marginRight, marginBottom: marginBottom, widthMode: widthMode, manualMagnification: manualMagnification, enforceMinHeight: enforceMinHeight, outlineText: outlineText, addQuietZoneGuides: addQuietZoneGuides};
            }
        } catch (e) {
            // File read error, use defaults
        }
    }
    return defaults;
}


function writeBWRPreference(bwr, link, removeWhite, heightMode, heightValue, quietZoneIndicator, marginTop, marginLeft, marginRight, marginBottom, widthMode, manualMagnification, enforceMinHeight, outlineText, addQuietZoneGuides) {
    try {
        var prefFile = new File(getPreferencePath());
        prefFile.open('w');
        prefFile.writeln(bwr.toString());
        prefFile.writeln(link.toString());
        prefFile.writeln(removeWhite.toString());
        prefFile.writeln(heightMode.toString());
        prefFile.writeln(heightValue.toString());
        prefFile.writeln(quietZoneIndicator.toString());
        prefFile.writeln(marginTop.toString());
        prefFile.writeln(marginLeft.toString());
        prefFile.writeln(marginRight.toString());
        prefFile.writeln(marginBottom.toString());
        prefFile.writeln(widthMode.toString());
        prefFile.writeln(manualMagnification.toString());
        prefFile.writeln(enforceMinHeight.toString());
        prefFile.writeln(outlineText.toString());
        prefFile.writeln(addQuietZoneGuides.toString());
        prefFile.close();
        return true;
    } catch (e) {
        alert("Could not save preference: " + e.message);
        return false;
    }
}


function showBWRDialog() {
    // Get current preferences to pre-select in dialog
    var currentPrefs = readBWRPreference();

    // Create dialog
    var dialog = new Window("dialog", "Barcode Print Settings");
    dialog.alignChildren = "fill";
    dialog.spacing = 8;
    dialog.margins = 12;

    // Header with Restore Defaults button
    var headerGroup = dialog.add("group");
    headerGroup.orientation = "row";
    headerGroup.alignChildren = ["fill", "center"];
    headerGroup.alignment = ["fill", "top"];

    var spacer = headerGroup.add("group");
    spacer.preferredSize = [0, 0];
    spacer.alignment = ["fill", "center"];

    var restoreDefaultsBtn = headerGroup.add("button", undefined, "Restore Defaults");
    restoreDefaultsBtn.alignment = ["right", "center"];

    // === Width Mode Panel ===
    var widthPanel = dialog.add("panel", undefined, "Width Mode");
    widthPanel.alignChildren = "left";
    widthPanel.margins = [10, 15, 10, 15]; // [left, top, right, bottom] for vertical centering
    widthPanel.spacing = 5;

    var autoRadio = widthPanel.add("radiobutton", undefined, "Auto-detect from text frame");
    var manualRadio = widthPanel.add("radiobutton", undefined, "Manual magnification");

    // Set default based on saved preference
    if (currentPrefs.widthMode === "manual") {
        manualRadio.value = true;
    } else {
        autoRadio.value = true;
    }

    // Magnification dropdown (only for manual mode)
    var magnificationGroup = widthPanel.add("group");
    magnificationGroup.orientation = "row";
    magnificationGroup.alignChildren = ["left", "center"];
    magnificationGroup.spacing = 5;
    var scaleLabel = magnificationGroup.add("statictext", undefined, "   Scale:");
    scaleLabel.characters = 10;
    var magnificationDropdown = magnificationGroup.add("dropdownlist", undefined, [
        "80%", "85%", "90%", "95%", "100%", "105%", "110%", "115%", "120%",
        "130%", "140%", "150%", "160%", "170%", "180%", "190%", "200%"
    ]);

    // Map current preference to dropdown
    var magnificationValues = [80, 85, 90, 95, 100, 105, 110, 115, 120, 130, 140, 150, 160, 170, 180, 190, 200];
    var currentMag = currentPrefs.manualMagnification || 100;
    var magnificationIndex = 4; // Default to 100%
    for (var i = 0; i < magnificationValues.length; i++) {
        if (magnificationValues[i] === currentMag) {
            magnificationIndex = i;
            break;
        }
    }
    magnificationDropdown.selection = magnificationIndex;

    // Always show dropdown, but enable/disable based on mode
    magnificationDropdown.enabled = manualRadio.value;

    // Update enabled state on radio change
    autoRadio.onClick = function() {
        magnificationDropdown.enabled = false;
    };
    manualRadio.onClick = function() {
        magnificationDropdown.enabled = true;
    };

    var infoLabel = widthPanel.add("statictext", undefined, "All barcodes are GS1-compliant (80-200% of nominal)");

    // === Height Panel ===
    var heightPanel = dialog.add("panel", undefined, "Barcode Height");
    heightPanel.alignChildren = "left";
    heightPanel.margins = [10, 15, 10, 15]; // [left, top, right, bottom] for vertical centering
    heightPanel.spacing = 5;

    var proportionalRadio = heightPanel.add("radiobutton", undefined, "Proportional (Height = Width / divisor)");
    var fixedRadio = heightPanel.add("radiobutton", undefined, "Fixed (Height in document units)");

    // Pre-select based on current preference
    if (currentPrefs.heightMode === "fixed") {
        fixedRadio.value = true;
    } else {
        proportionalRadio.value = true;
    }

    // Height value input
    var heightInputGroup = heightPanel.add("group");
    heightInputGroup.orientation = "row";
    heightInputGroup.alignChildren = ["left", "center"];
    var heightInputLabel = heightInputGroup.add("statictext", undefined, "Value:");
    heightInputLabel.characters = 10;
    var heightInput = heightInputGroup.add("edittext", undefined, currentPrefs.heightValue.toFixed(2));
    heightInput.characters = 10;

    // Enforce minimum height checkbox
    var enforceMinHeightCheckbox = heightPanel.add("checkbox", undefined, "Enforce GS1 minimum height (~22.85mm at 100%)");
    enforceMinHeightCheckbox.value = currentPrefs.enforceMinHeight;

    // Update divisor when enforcement checkbox changes (only in proportional mode)
    enforceMinHeightCheckbox.onClick = function() {
        if (proportionalRadio.value && enforceMinHeightCheckbox.value) {
            // Checked + Proportional: set to GS1-compliant divisor (1.63)
            heightInput.text = "1.63";
        }
    };

    // Also update when switching to proportional mode if enforcement is checked
    proportionalRadio.onClick = function() {
        if (enforceMinHeightCheckbox.value) {
            heightInput.text = "1.63";
        }
    };

    // === Options Panel ===
    var optionsPanel = dialog.add("panel", undefined, "Options");
    optionsPanel.alignChildren = "left";
    optionsPanel.margins = [10, 15, 10, 15]; // [left, top, right, bottom] for vertical centering
    optionsPanel.spacing = 5;

    var linkCheckbox = optionsPanel.add("checkbox", undefined, "Save as linked EPS files (requires saved document)");
    linkCheckbox.value = currentPrefs.link;

    var outlineTextCheckbox = optionsPanel.add("checkbox", undefined, "Convert text to outlines (print-ready)");
    outlineTextCheckbox.value = currentPrefs.outlineText;

    var quietZoneGuidesCheckbox = optionsPanel.add("checkbox", undefined, "Add quiet zone guides");
    quietZoneGuidesCheckbox.value = currentPrefs.addQuietZoneGuides;

    var quietZoneCheckbox = optionsPanel.add("checkbox", undefined, "Add quiet zone indicator (> symbol)");
    quietZoneCheckbox.value = currentPrefs.quietZoneIndicator;

    var removeWhiteCheckbox = optionsPanel.add("checkbox", undefined, "Remove white background rectangle");
    removeWhiteCheckbox.value = currentPrefs.removeWhite;

    // White rectangle margins - compact 2x2 layout
    var unitName = getDocUnitName();
    var marginLabel = optionsPanel.add("statictext", undefined, "White rectangle margins (" + unitName + "):");

    // Chain mode selection
    var chainGroup = optionsPanel.add("group");
    chainGroup.orientation = "row";
    chainGroup.alignChildren = ["left", "center"];
    chainGroup.spacing = 10;
    var chainLabel = chainGroup.add("statictext", undefined, "  Chain:");
    chainLabel.characters = 10;
    var chainAllRadio = chainGroup.add("radiobutton", undefined, "All");
    var chainPairsRadio = chainGroup.add("radiobutton", undefined, "Pairs (T/B, L/R)");
    var chainNoneRadio = chainGroup.add("radiobutton", undefined, "Independent");
    chainNoneRadio.value = true; // Default to independent

    // Helper function to format margin values (remove trailing zeros)
    function formatMarginValue(value) {
        var formatted = value.toFixed(2);
        // Remove trailing zeros and decimal point if not needed
        formatted = formatted.replace(/\.?0+$/, '');
        if (formatted === '') formatted = '0';
        return formatted;
    }

    // Top/Bottom row
    var marginRow1 = optionsPanel.add("group");
    marginRow1.orientation = "row";
    marginRow1.alignChildren = ["left", "center"];
    marginRow1.spacing = 10;
    marginRow1.add("statictext", undefined, "  Top:").characters = 8;
    var marginTopInput = marginRow1.add("edittext", undefined, formatMarginValue(pointsToDocUnits(currentPrefs.marginTop)));
    marginTopInput.characters = 6;
    marginRow1.add("statictext", undefined, "  Bottom:").characters = 8;
    var marginBottomInput = marginRow1.add("edittext", undefined, formatMarginValue(pointsToDocUnits(currentPrefs.marginBottom)));
    marginBottomInput.characters = 6;

    // Left/Right row
    var marginRow2 = optionsPanel.add("group");
    marginRow2.orientation = "row";
    marginRow2.alignChildren = ["left", "center"];
    marginRow2.spacing = 10;
    marginRow2.add("statictext", undefined, "  Left:").characters = 8;
    var marginLeftInput = marginRow2.add("edittext", undefined, formatMarginValue(pointsToDocUnits(currentPrefs.marginLeft)));
    marginLeftInput.characters = 6;
    marginRow2.add("statictext", undefined, "  Right:").characters = 8;
    var marginRightInput = marginRow2.add("edittext", undefined, formatMarginValue(pointsToDocUnits(currentPrefs.marginRight)));
    marginRightInput.characters = 6;

    // Chain functionality - sync values when inputs change
    function updateChainedMargins(changedInput, isTopBottom) {
        if (chainAllRadio.value) {
            // Chain all - update all four fields
            var value = changedInput.text;
            marginTopInput.text = value;
            marginBottomInput.text = value;
            marginLeftInput.text = value;
            marginRightInput.text = value;
        } else if (chainPairsRadio.value) {
            // Chain pairs - update only the paired field
            var value = changedInput.text;
            if (isTopBottom) {
                if (changedInput === marginTopInput) {
                    marginBottomInput.text = value;
                } else {
                    marginTopInput.text = value;
                }
            } else {
                if (changedInput === marginLeftInput) {
                    marginRightInput.text = value;
                } else {
                    marginLeftInput.text = value;
                }
            }
        }
        // If chainNoneRadio.value, do nothing - fields are independent
    }

    // Add onChange handlers for chaining
    marginTopInput.onChanging = function() { updateChainedMargins(this, true); };
    marginBottomInput.onChanging = function() { updateChainedMargins(this, true); };
    marginLeftInput.onChanging = function() { updateChainedMargins(this, false); };
    marginRightInput.onChanging = function() { updateChainedMargins(this, false); };

    // === Bar Width Reduction Panel ===
    var printPanel = dialog.add("panel", undefined, "Bar Width Reduction (BWR)");
    printPanel.alignChildren = "left";
    printPanel.margins = [10, 15, 10, 15]; // [left, top, right, bottom] for vertical centering
    printPanel.spacing = 5;

    var warningIcon = printPanel.add("statictext", undefined, "\u26A0\uFE0F BWR is printer-specific. Consult your printer for the correct value.", {multiline: true});
    warningIcon.characters = 50;

    var infoText = printPanel.add("statictext", undefined, "\u2139\uFE0F Professional printers regenerate barcodes with verified BWR values.", {multiline: true});
    infoText.characters = 50;

    // Single BWR input field
    var bwrInputGroup = printPanel.add("group");
    bwrInputGroup.orientation = "row";
    bwrInputGroup.alignChildren = ["left", "center"];
    bwrInputGroup.spacing = 5;
    bwrInputGroup.add("statictext", undefined, "BWR:");
    var bwrInput = bwrInputGroup.add("edittext", undefined, currentPrefs.bwr.toString());
    bwrInput.characters = 5;
    bwrInputGroup.add("statictext", undefined, "% (0-20%)");

    // Help text
    var helpGroup = dialog.add("group");
    helpGroup.orientation = "column";
    helpGroup.alignChildren = "left";
    var help = helpGroup.add("statictext", undefined, "Tip: Hold Option/Alt when running the script to show this dialog again.", {multiline: true});
    help.characters = 60;

    var learnMore = helpGroup.add("statictext", undefined, "Learn more: productivista.com/ean-13-in-adobe-illustrator", {multiline: true});
    learnMore.characters = 60;

    // Buttons
    var buttonGroup = dialog.add("group");
    buttonGroup.alignment = "center";
    var okBtn = buttonGroup.add("button", undefined, "OK", {name: "ok"});
    var cancelBtn = buttonGroup.add("button", undefined, "Cancel", {name: "cancel"});

    // Restore Defaults button handler
    restoreDefaultsBtn.onClick = function() {
        // Width Mode
        autoRadio.value = true;
        manualRadio.value = false;
        magnificationDropdown.enabled = false;
        magnificationDropdown.selection = 4; // 100%

        // Height
        proportionalRadio.value = true;
        fixedRadio.value = false;
        heightInput.text = "1.63"; // GS1-compliant divisor (gives 22.85mm at 100%)
        enforceMinHeightCheckbox.value = true;

        // Options
        linkCheckbox.value = false;
        quietZoneCheckbox.value = false;
        removeWhiteCheckbox.value = false;
        outlineTextCheckbox.value = false;
        quietZoneGuidesCheckbox.value = false;
        chainNoneRadio.value = true;
        chainAllRadio.value = false;
        chainPairsRadio.value = false;
        marginTopInput.text = "0";
        marginLeftInput.text = "0";
        marginRightInput.text = "0";
        marginBottomInput.text = "0";

        // BWR
        bwrInput.text = "0";
    };

    // Show dialog
    if (dialog.show() == 1) {
        // OK clicked - determine selected values
        // Parse BWR value from single input field
        var selectedBWR = 0;
        var bwrText = bwrInput.text.replace(/,/g, '.');
        if (bwrText.length > 0) {
            var bwrValue = parseFloat(bwrText);
            if (!isNaN(bwrValue) && bwrValue >= 0 && bwrValue <= 20) {
                selectedBWR = bwrValue;
            } else {
                alert("BWR must be between 0% and 20%. Using 0%.");
                selectedBWR = 0;
            }
        }

        var selectedLink = linkCheckbox.value;
        var selectedRemoveWhite = removeWhiteCheckbox.value;
        var selectedQuietZoneIndicator = quietZoneCheckbox.value;
        var selectedOutlineText = outlineTextCheckbox.value;
        var selectedQuietZoneGuides = quietZoneGuidesCheckbox.value;

        // Extract width mode and manual magnification
        var selectedWidthMode = autoRadio.value ? "auto" : "manual";
        var selectedManualMagnification = 100; // Default
        if (manualRadio.value && magnificationDropdown.selection !== null) {
            var magText = magnificationDropdown.selection.text.replace("%", "");
            selectedManualMagnification = parseInt(magText);
        }

        // Determine selected height mode
        var selectedHeightMode = proportionalRadio.value ? "proportional" : "fixed";
        var selectedEnforceMinHeight = enforceMinHeightCheckbox.value;

        // Parse and validate height value
        var selectedHeightValue = 3.3; // Default divisor
        var heightText = heightInput.text.replace(/,/g, '.'); // Replace comma with dot for decimal
        if (heightText.length > 0) {
            var parsedValue = parseFloat(heightText);
            if (!isNaN(parsedValue) && parsedValue > 0) {
                if (selectedHeightMode === "proportional") {
                    // Proportional mode: divisor must be >= 1
                    if (parsedValue >= 1) {
                        selectedHeightValue = parsedValue;
                    } else {
                        alert("Divisor must be at least 1. Using default divisor 3.3.");
                        selectedHeightValue = 3.3;
                    }
                } else {
                    // Fixed mode: height must be >= 5 document units
                    if (parsedValue >= 5) {
                        selectedHeightValue = parsedValue;
                    } else {
                        alert("Height must be at least 5 document units for scannable barcodes. Using minimum height 5.");
                        selectedHeightValue = 5;
                    }
                }
            } else {
                alert("Invalid height value. Using default (divisor 3.3).");
            }
        }

        // Parse and validate margin values (convert from document units to points)
        var selectedMarginTop = 0;
        var selectedMarginLeft = 0;
        var selectedMarginRight = 0;
        var selectedMarginBottom = 0;

        var marginTopText = marginTopInput.text.replace(/,/g, '.');
        if (marginTopText.length > 0) {
            var parsedMarginTop = parseFloat(marginTopText);
            if (!isNaN(parsedMarginTop) && parsedMarginTop >= 0) {
                selectedMarginTop = docUnitsToPoints(parsedMarginTop);
            }
        }

        var marginLeftText = marginLeftInput.text.replace(/,/g, '.');
        if (marginLeftText.length > 0) {
            var parsedMarginLeft = parseFloat(marginLeftText);
            if (!isNaN(parsedMarginLeft) && parsedMarginLeft >= 0) {
                selectedMarginLeft = docUnitsToPoints(parsedMarginLeft);
            }
        }

        var marginRightText = marginRightInput.text.replace(/,/g, '.');
        if (marginRightText.length > 0) {
            var parsedMarginRight = parseFloat(marginRightText);
            if (!isNaN(parsedMarginRight) && parsedMarginRight >= 0) {
                selectedMarginRight = docUnitsToPoints(parsedMarginRight);
            }
        }

        var marginBottomText = marginBottomInput.text.replace(/,/g, '.');
        if (marginBottomText.length > 0) {
            var parsedMarginBottom = parseFloat(marginBottomText);
            if (!isNaN(parsedMarginBottom) && parsedMarginBottom >= 0) {
                selectedMarginBottom = docUnitsToPoints(parsedMarginBottom);
            }
        }

        // Save preferences
        writeBWRPreference(selectedBWR, selectedLink, selectedRemoveWhite, selectedHeightMode, selectedHeightValue, selectedQuietZoneIndicator, selectedMarginTop, selectedMarginLeft, selectedMarginRight, selectedMarginBottom, selectedWidthMode, selectedManualMagnification, selectedEnforceMinHeight, selectedOutlineText, selectedQuietZoneGuides);

        return {bwr: selectedBWR, link: selectedLink, removeWhite: selectedRemoveWhite, heightMode: selectedHeightMode, heightValue: selectedHeightValue, quietZoneIndicator: selectedQuietZoneIndicator, marginTop: selectedMarginTop, marginLeft: selectedMarginLeft, marginRight: selectedMarginRight, marginBottom: selectedMarginBottom, widthMode: selectedWidthMode, manualMagnification: selectedManualMagnification, enforceMinHeight: selectedEnforceMinHeight, outlineText: selectedOutlineText, addQuietZoneGuides: selectedQuietZoneGuides};
    } else {
        // Cancel clicked
        return null;
    }
}


function showValidationSummary(results) {
    // Filter non-compliant barcodes
    var compliantCount = 0;
    var nonCompliantBarcodes = [];

    for (var i = 0; i < results.length; i++) {
        var result = results[i];
        if (result.isCompliant === true) {
            compliantCount++;
        } else if (result.isCompliant === false) {
            nonCompliantBarcodes.push(result);
        }
    }

    // Only show summary if there are non-compliant barcodes
    if (nonCompliantBarcodes.length === 0) {
        return; // All compliant, silent success
    }

    // Build summary dialog
    var summaryDialog = new Window("dialog", "GS1 Compliance Summary");
    summaryDialog.alignChildren = "fill";
    summaryDialog.margins = 15;
    summaryDialog.spacing = 10;

    var headerText = summaryDialog.add("statictext", undefined, "Generated " + results.length + " barcode" + (results.length > 1 ? "s" : "") + " in CUSTOM SIZE mode");
    headerText.graphics.font = ScriptUI.newFont(headerText.graphics.font.name, ScriptUI.FontStyle.BOLD, headerText.graphics.font.size + 1);

    summaryDialog.add("statictext", undefined, ""); // Spacer

    if (compliantCount > 0) {
        summaryDialog.add("statictext", undefined, "\u2713 " + compliantCount + " barcode" + (compliantCount > 1 ? "s" : "") + " GS1-compliant");
    }

    var warningText = summaryDialog.add("statictext", undefined, "\u26A0\uFE0F " + nonCompliantBarcodes.length + " barcode" + (nonCompliantBarcodes.length > 1 ? "s" : "") + " outside GS1 range:");
    warningText.graphics.foregroundColor = warningText.graphics.newPen(warningText.graphics.PenType.SOLID_COLOR, [1, 0.5, 0], 1); // Orange

    // List non-compliant barcodes
    var listPanel = summaryDialog.add("panel", undefined, "");
    listPanel.alignChildren = "left";
    listPanel.margins = 10;

    for (var i = 0; i < nonCompliantBarcodes.length && i < 5; i++) { // Show max 5 to prevent dialog overflow
        var barcode = nonCompliantBarcodes[i];
        var barcodeText = listPanel.add("statictext", undefined, "  \u2022 " + barcode.ean + ": " + barcode.widthMM.toFixed(2) + "mm (" + barcode.percentOfNominal + "% nominal)", {multiline: true});
        barcodeText.characters = 50;

        var reason = "";
        if (barcode.widthMM < barcode.minMM) {
            reason = "     \u2192 Below minimum " + barcode.minMM.toFixed(2) + "mm";
        } else if (barcode.widthMM > barcode.maxMM) {
            reason = "     \u2192 Above maximum " + barcode.maxMM.toFixed(2) + "mm";
        }
        listPanel.add("statictext", undefined, reason);
    }

    if (nonCompliantBarcodes.length > 5) {
        listPanel.add("statictext", undefined, "  ... and " + (nonCompliantBarcodes.length - 5) + " more");
    }

    summaryDialog.add("statictext", undefined, ""); // Spacer

    var infoText = summaryDialog.add("statictext", undefined, "\u2139\uFE0F Non-compliant barcodes may fail retail scanning", {multiline: true});
    infoText.characters = 50;

    var tipText = summaryDialog.add("statictext", undefined, "\uD83D\uDCA1 Tip: Use GS1 Standard mode for production", {multiline: true});
    tipText.characters = 50;

    summaryDialog.add("statictext", undefined, ""); // Spacer

    var buttonGroup = summaryDialog.add("group");
    buttonGroup.alignment = "center";
    var okBtn = buttonGroup.add("button", undefined, "OK", {name: "ok"});

    summaryDialog.show();
}


////////////////          LINKED FILES FUNCTIONS        /////////////////////////////////////////////////////////////////


function checkDocumentSaved() {
    // Check if document has been saved to disk
    var hasBeenSaved = false;
    try {
        hasBeenSaved = (doc.fullName && doc.fullName.exists);
    } catch (e) {
        hasBeenSaved = false;
    }

    if (!hasBeenSaved) {
        var saveDialog = new Window("dialog", "Document Not Saved");
        saveDialog.alignChildren = "left";

        var msgGroup = saveDialog.add("group");
        msgGroup.orientation = "column";
        msgGroup.alignChildren = "left";

        var msg1 = msgGroup.add("statictext", undefined, "This document must be saved before creating linked barcodes.");
        var msg2 = msgGroup.add("statictext", undefined, "");
        var msg3 = msgGroup.add("statictext", undefined, "What would you like to do?");
        msg1.characters = 50;
        msg3.characters = 50;

        saveDialog.add("panel", undefined, "");

        var buttonGroup = saveDialog.add("group");
        buttonGroup.alignment = "center";
        var saveBtn = buttonGroup.add("button", undefined, "Save Document");
        var embedBtn = buttonGroup.add("button", undefined, "Use Embedded");
        var cancelBtn = buttonGroup.add("button", undefined, "Cancel");

        var result = saveDialog.show();

        if (result == 1) {
            // Save button clicked - show save as dialog
            try {
                var saveFile = File.saveDialog("Save document as", "Adobe Illustrator:*.ai");
                if (saveFile) {
                    doc.saveAs(saveFile);
                    return true;
                } else {
                    // User cancelled save dialog
                    return false;
                }
            } catch (e) {
                alert("Could not save document: " + e.message);
                return false;
            }
        } else if (result == 2) {
            // Use Embedded button clicked
            return false;
        } else {
            // Cancel button clicked
            exit();
        }
    }
    return true;
}


function getBarcodeFolder() {
    // Get document folder and create Barcodes subfolder
    try {
        var docFile = new File(doc.fullName);
        var docFolder = docFile.parent;
        var barcodeFolder = new Folder(docFolder.fsName + "/Barcodes");

        if (!barcodeFolder.exists) {
            if (!barcodeFolder.create()) {
                alert("Could not create Barcodes folder. Using embedded barcodes instead.");
                return null;
            }
        }

        return barcodeFolder;
    } catch (e) {
        alert("Error accessing document folder: " + e.message + ". Using embedded barcodes.");
        return null;
    }
}


function exportBarcodeAsEPS(barcodeGroup, eanNumber, barcodeFolder) {
    // Export barcode group as EPS file
    try {
        var epsFile = new File(barcodeFolder.fsName + "/" + eanNumber + ".eps");

        // Always regenerate EPS to reflect current settings
        // Remove existing file if present
        if (epsFile.exists) {
            epsFile.remove();
        }

        // Create temporary document for export
        var tempDoc = app.documents.add(DocumentColorSpace.CMYK);

        // Duplicate barcode group to temp document
        var tempGroup = barcodeGroup.duplicate(tempDoc, ElementPlacement.PLACEATEND);

        // Fit artboard to artwork
        tempDoc.artboards[0].artboardRect = tempGroup.geometricBounds;

        // Export as EPS
        var epsOptions = new EPSSaveOptions();
        epsOptions.cmykPostScript = true;
        epsOptions.embedAllFonts = true;
        epsOptions.compatibility = Compatibility.ILLUSTRATOR10;

        tempDoc.saveAs(epsFile, epsOptions);
        tempDoc.close(SaveOptions.DONOTSAVECHANGES);

        return epsFile;
    } catch (e) {
        alert("Error exporting barcode " + eanNumber + ": " + e.message);
        return null;
    }
}


function placeBarcodeLink(epsFile, position, angle, rotoCenterX, rotoCenterY) {
    // Place linked EPS file at specified position and rotation
    try {
        var placedItem = doc.placedItems.add();
        placedItem.file = epsFile;

        // Position the placed item
        placedItem.position = position;

        // Apply rotation if needed (using same rotation center as embedded barcode)
        if (angle !== 0) {
            rotate_around_point(placedItem, rotoCenterY, rotoCenterX, angle);
        }

        return placedItem;
    } catch (e) {
        alert("Error placing linked barcode: " + e.message);
        return null;
    }
}


