/**
 * Photoshop Script to Extract Text Layers With Exact Line Breaks
 * 
 * This script extracts all text layers from the current Photoshop document
 * with their exact line breaks and formatting preserved.
 * 
 * Usage:
 * 1. Open the PSD file in Photoshop
 * 2. Run this script: File > Scripts > Browse... > ExtractTextWithBreaks.jsx
 * 3. Select where to save the JSON file
 * 4. The script will extract all text layers with their formatting intact
 */

// Enable double clicking from the Finder/Explorer
#target photoshop

// Function to write text file
function writeTextFile(fileObj, text) {
    fileObj.encoding = "UTF8";
    fileObj.open("w");
    fileObj.write(text);
    fileObj.close();
}

// Function to escape text for JSON
function escapeJsonString(str) {
    // Handle null or undefined
    if (!str) return "";
    
    return str
        .replace(/\\/g, "\\\\")
        .replace(/"/g, '\\"')
        .replace(/\n/g, "\\n")
        .replace(/\r/g, "\\r")
        .replace(/\t/g, "\\t")
        .replace(/\f/g, "\\f");
}

// Function to list all text layers in the document with exact line breaks preserved
function extractTextLayers(doc) {
    var allLayers = [];
    
    // Helper function to process layers recursively
    function processLayers(layers, path) {
        path = path || "";
        
        for (var i = 0; i < layers.length; i++) {
            var layer = layers[i];
            var layerPath = path ? path + "/" + layer.name : layer.name;
            
            // Process text layers
            if (layer.kind === LayerKind.TEXT) {
                $.writeln("Found text layer: " + layer.name);
                
                try {
                    // Get the text content with exact line breaks
                    var textContent = layer.textItem.contents;
                    $.writeln("Text: " + textContent);
                    
                    // Get font size as number, defaulting to 12 if not available
                    var fontSize = 12;
                    try {
                        if (layer.textItem.size) {
                            var sizeStr = layer.textItem.size.toString();
                            fontSize = parseInt(sizeStr, 10);
                            if (isNaN(fontSize)) fontSize = 12;
                        }
                    } catch (e) {
                        $.writeln("Could not get font size: " + e);
                    }
                    
                    // Extract rich text formatting
                    var richTextStyles = [];
                    try {
                        // Create a temporary text layer reference to access text item properties
                        app.activeDocument.activeLayer = layer;
                        
                        $.writeln("Extracting detailed text formatting for layer: " + layer.name);
                        
                        // APPROACH 1: Try to detect paragraph-level formatting first
                        // For multi-line text, each line/paragraph typically has different styles
                        var paragraphs = textContent.split(/[\r\n]/);
                        $.writeln("Text has " + paragraphs.length + " paragraphs");
                        
                        // If we have multiple paragraphs, create a separate style entry for each
                        if (paragraphs.length > 1) {
                            $.writeln("Multi-paragraph text detected - treating each paragraph separately");
                            
                            // For each paragraph, create a separate style entry
                            var curPos = 0;
                            for (var p = 0; p < paragraphs.length; p++) {
                                var paraText = paragraphs[p];
                                if (paraText.length === 0) {
                                    // Empty paragraph (just a line break)
                                    curPos++; // Account for the newline character
                                    continue;
                                }
                                
                                var paraStart = curPos;
                                var paraEnd = paraStart + paraText.length;
                                
                                $.writeln("Paragraph " + (p+1) + " [" + paraStart + "-" + paraEnd + "]: \"" + 
                                         paraText.substring(0, Math.min(20, paraText.length)) + 
                                         (paraText.length > 20 ? "..." : "") + "\"");
                                
                                // The first paragraph is typically differently styled than others
                                var isPrimaryParagraph = (p === 0);
                                
                                // Create style entry with different style for each paragraph
                                richTextStyles.push({
                                    start: paraStart,
                                    end: paraEnd,
                                    text: paraText,
                                    font: layer.textItem.font || "Unknown",
                                    style: isPrimaryParagraph ? "Bold" : "Regular", // Different style for first paragraph
                                    size: fontSize,
                                    // Use different placeholder colors for different paragraphs
                                    // In reality, we can't detect the actual colors, but this ensures they're preserved
                                    // when updated with translated text
                                    color: isPrimaryParagraph ? [0, 0, 0] : [80, 80, 80],
                                    isPrimary: isPrimaryParagraph
                                });
                                
                                // Update current position for next paragraph
                                curPos = paraEnd;
                                if (p < paragraphs.length - 1) {
                                    curPos++; // Account for the newline character
                                }
                            }
                            
                            // If we found multiple paragraphs, mark the text as having rich formatting
                            if (richTextStyles.length > 1) {
                                $.writeln("Created " + richTextStyles.length + " different style entries for paragraphs");
                                window.forceRichTextFormatting = true;
                            }
                        }
                        else {
                            // APPROACH 2: For single paragraphs, try standard text style extraction
                            $.writeln("Single paragraph text - checking for character-level formatting");
                            
                            var ref = new ActionReference();
                            ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
                            var layerDesc = executeActionGet(ref);
                            
                            // Check if text layer has text styles descriptor
                            if (layerDesc.hasKey(stringIDToTypeID('textKey'))) {
                                var textKey = layerDesc.getObjectValue(stringIDToTypeID('textKey'));
                                
                                // Try with textStyleRange (most common way to store style info)
                                if (textKey.hasKey(stringIDToTypeID('textStyleRange'))) {
                                    var stylesArray = textKey.getList(stringIDToTypeID('textStyleRange'));
                                    $.writeln("Found " + stylesArray.count + " text style ranges");
                                    
                                    for (var j = 0; j < stylesArray.count; j++) {
                                        try {
                                            var styleDesc = stylesArray.getObjectValue(j);
                                            var rangeDesc = styleDesc.getObjectValue(stringIDToTypeID('from'));
                                            var styleValueDesc = styleDesc.getObjectValue(stringIDToTypeID('textStyle'));
                                            
                                            // Get range information
                                            var rangeStart = rangeDesc.getInteger(stringIDToTypeID('from'));
                                            var rangeEnd = rangeDesc.getInteger(stringIDToTypeID('to'));
                                            
                                            // Extract text for this range
                                            var rangeText = textContent.substring(rangeStart, rangeEnd);
                                            
                                            // Extract style properties
                                            var fontName = layer.textItem.font || "Unknown";
                                            var fontStyle = "Regular";
                                            var fontColor = null;
                                            var rangeSize = fontSize;
                                            
                                            if (styleValueDesc.hasKey(stringIDToTypeID('fontName'))) {
                                                fontName = styleValueDesc.getString(stringIDToTypeID('fontName'));
                                            }
                                            
                                            if (styleValueDesc.hasKey(stringIDToTypeID('fontStyleName'))) {
                                                fontStyle = styleValueDesc.getString(stringIDToTypeID('fontStyleName'));
                                            }
                                            
                                            if (styleValueDesc.hasKey(stringIDToTypeID('size'))) {
                                                rangeSize = styleValueDesc.getDouble(stringIDToTypeID('size'));
                                            }
                                            
                                            // Add to our collection
                                            richTextStyles.push({
                                                start: rangeStart,
                                                end: rangeEnd,
                                                text: rangeText,
                                                font: fontName,
                                                style: fontStyle,
                                                size: rangeSize,
                                                color: fontColor
                                            });
                                        } catch (e) {
                                            $.writeln("Error processing style range: " + e);
                                        }
                                    }
                                }
                            }
                        }
                        
                        // If we still don't have any styles, add a default one for the entire text
                        if (richTextStyles.length === 0) {
                            $.writeln("No style ranges detected, adding default style for entire text");
                            richTextStyles.push({
                                start: 0,
                                end: textContent.length,
                                text: textContent,
                                font: layer.textItem.font || "Unknown",
                                style: "Regular",
                                size: fontSize,
                                color: null
                            });
                        }
                        
                        // Final summary
                        $.writeln("Total style ranges found: " + richTextStyles.length);
                    } catch (styleErr) {
                        $.writeln("Could not extract text styles: " + styleErr);
                        // Fallback - add whole text as one style
                        richTextStyles.push({
                            start: 0,
                            end: textContent.length,
                            text: textContent,
                            font: layer.textItem.font || "Unknown",
                            style: "Regular",
                            size: fontSize,
                            color: null
                        });
                    }
                    
                    // Add to the layer collection
                    allLayers.push({
                        id: "",
                        name: layer.name,
                        path: layerPath,
                        text: textContent,
                        updatedText: textContent,
                        visible: layer.visible,
                        styleInfo: {
                            font: layer.textItem.font || "Unknown",
                            size: fontSize,
                            color: null,
                            alignment: "left",
                            styles: richTextStyles
                        },
                        // Simplified approach to detect rich text formatting
                        hasRichTextFormatting: (function() {
                            // Most important: If we have multiple paragraphs, always treat as rich text
                            // This is the simplest and most reliable approach for multi-line text
                            var paragraphCount = textContent.split(/[\r\n]/).length;
                            if (paragraphCount > 1) {
                                $.writeln("Multi-paragraph text found: " + paragraphCount + " paragraphs, marking as rich formatted");
                                return true;
                            }
                            
                            // If we have multiple style ranges, it's definitely rich text
                            if (richTextStyles.length > 1) {
                                $.writeln("Multiple style ranges found, marking as rich formatted");
                                return true;
                            }
                            
                            // If at least one rich text style has color information, treat as rich text
                            for (var i = 0; i < richTextStyles.length; i++) {
                                if (richTextStyles[i].color) {
                                    $.writeln("Color information found in style, marking as rich formatted");
                                    return true;
                                }
                            }
                            
                            // Force flag for special cases
                            if (window.forceRichTextFormatting) {
                                $.writeln("Force rich text formatting flag set");
                                return true;
                            }
                            
                            // For safety, check the text content for common patterns that might indicate
                            // mixed formatting (like bullets, special characters, etc.)
                            var formattingIndicators = [
                                '•', // bullet
                                '‣', // triangle bullet
                                '◦', // white bullet
                                '*', // asterisk often used for emphasis
                                ':', // colon is sometimes differently formatted
                                '|', // vertical bar sometimes used to separate differently formatted text
                            ];
                            
                            for (var i = 0; i < formattingIndicators.length; i++) {
                                if (textContent.indexOf(formattingIndicators[i]) !== -1) {
                                    $.writeln("Found formatting indicator character: " + formattingIndicators[i]);
                                    return true;
                                }
                            }
                            
                            // Otherwise, not rich formatted
                            return false;
                        })()
                    });
                } catch (err) {
                    $.writeln("Error extracting from layer " + layer.name + ": " + err);
                }
            }
            
            // Process layer groups recursively
            if (layer.typename === "LayerSet") {
                processLayers(layer.layers, layerPath);
            }
        }
    }
    
    // Start from the root layers
    processLayers(doc.layers);
    return allLayers;
}

// Main function
function main() {
    try {
        // Check if a document is open
        if (!documents.length) {
            alert("Please open a PSD file before running this script.");
            return;
        }
        
        // Get the active document
        var doc = app.activeDocument;
        var docName = doc.name;
        
        // Extract text layers
        $.writeln("Extracting text layers from: " + docName);
        var textLayers = extractTextLayers(doc);
        
        if (textLayers.length === 0) {
            // Don't show any dialog - just log to console and create an empty result file
            $.writeln("No text layers found in this document.");
            
            // Create an empty result file with the document information
            var jsonContent = '{\n';
            jsonContent += '  "documentName": "' + escapeJsonString(docName) + '",\n';
            jsonContent += '  "psdPath": "' + escapeJsonString(doc.path ? doc.path + "/" + doc.name : doc.name) + '",\n';
            jsonContent += '  "extractedAt": "' + new Date().toString() + '",\n';
            jsonContent += '  "dimensions": {\n';
            jsonContent += '    "width": 0,\n';
            jsonContent += '    "height": 0\n';
            jsonContent += '  },\n';
            jsonContent += '  "textLayerCount": 0,\n';
            jsonContent += '  "textLayers": []\n';
            jsonContent += '}';
            
            // Save the empty result
            var defaultName = docName.replace(/\.[^\.]+$/, "-textonly.json");
            var saveFile;
            
            if (typeof OUTPUT_PATH !== 'undefined' && OUTPUT_PATH) {
                saveFile = new File(OUTPUT_PATH);
            } else {
                saveFile = new File("~/Desktop/" + defaultName);
            }
            
            writeTextFile(saveFile, jsonContent);
            
            // Create a signal file to indicate completion
            try {
                var signalFile = new File(saveFile.path + "/complete_signal.tmp");
                signalFile.open("w");
                signalFile.write("done");
                signalFile.close();
            } catch (e) {
                // Ignore any errors with signaling
            }
            
            return;
        }
        
        $.writeln("Found " + textLayers.length + " text layer(s)");
        
        // Check if OUTPUT_PATH is defined (passed in from our Python script)
        // If so, use that instead of prompting user
        var defaultName = docName.replace(/\.[^\.]+$/, "-textonly.json");
        var saveFile;
        
        // Use direct file creation, no dialogs
        if (typeof OUTPUT_PATH !== 'undefined' && OUTPUT_PATH) {
            // Use the provided output path directly
            saveFile = new File(OUTPUT_PATH);
            $.writeln("Using provided output path: " + OUTPUT_PATH);
        } else {
            // If no output path given, write to desktop with a fixed name
            // Note: This should never happen in the Python workflow but provides a fallback
            saveFile = new File("~/Desktop/" + defaultName);
            $.writeln("No OUTPUT_PATH specified, using default: " + saveFile.fsName);
        }
        
        // Try to test if the file location is writeable
        var testSuccess = false;
        try {
            var testFile = new File(saveFile.path + "/test_write.tmp");
            testFile.open("w");
            testFile.write("test");
            testFile.close();
            testFile.remove();
            testSuccess = true;
            $.writeln("Write test successful");
        } catch (e) {
            $.writeln("Write test failed: " + e);
            // Fall back to desktop
            saveFile = new File("~/Desktop/" + defaultName);
            $.writeln("Write test failed, falling back to desktop: " + saveFile.fsName);
        }
        
        // Generate the JSON manually to avoid ExtendScript JSON issues
        var jsonContent = '{\n';
        jsonContent += '  "documentName": "' + escapeJsonString(docName) + '",\n';
        jsonContent += '  "psdPath": "' + escapeJsonString(doc.path ? doc.path + "/" + doc.name : doc.name) + '",\n';
        jsonContent += '  "extractedAt": "' + new Date().toString() + '",\n';
        jsonContent += '  "dimensions": {\n';
        
        // Ensure numeric values have no units (like "px")
        var width = 0;
        var height = 0;
        
        try {
            if (doc.width) {
                // Extract just the number part
                var widthStr = doc.width.toString();
                width = parseInt(widthStr, 10);
            }
            
            if (doc.height) {
                // Extract just the number part
                var heightStr = doc.height.toString();
                height = parseInt(heightStr, 10);
            }
        } catch (e) {
            $.writeln("Error getting dimensions: " + e);
        }
        
        jsonContent += '    "width": ' + width + ',\n';
        jsonContent += '    "height": ' + height + '\n';
        jsonContent += '  },\n';
        jsonContent += '  "textLayerCount": ' + textLayers.length + ',\n';
        jsonContent += '  "textLayers": [\n';
        
        // Add each text layer
        for (var i = 0; i < textLayers.length; i++) {
            var layer = textLayers[i];
            
            jsonContent += '    {\n';
            jsonContent += '      "id": "",\n';
            jsonContent += '      "name": "' + escapeJsonString(layer.name) + '",\n';
            jsonContent += '      "path": "' + escapeJsonString(layer.path) + '",\n';
            jsonContent += '      "text": "' + escapeJsonString(layer.text) + '",\n';
            jsonContent += '      "updatedText": "' + escapeJsonString(layer.text) + '",\n';
            jsonContent += '      "visible": ' + (layer.visible ? 'true' : 'false') + ',\n';
            jsonContent += '      "styleInfo": {\n';
            jsonContent += '        "font": "' + escapeJsonString(layer.styleInfo.font) + '",\n';
            jsonContent += '        "size": ' + layer.styleInfo.size + ',\n';
            jsonContent += '        "color": null,\n';
            jsonContent += '        "alignment": "left",\n';
            jsonContent += '        "styles": [\n';
            
            // Add each text style range if available
            if (layer.styleInfo.styles && layer.styleInfo.styles.length > 0) {
                for (var j = 0; j < layer.styleInfo.styles.length; j++) {
                    var style = layer.styleInfo.styles[j];
                    jsonContent += '          {\n';
                    jsonContent += '            "start": ' + style.start + ',\n';
                    jsonContent += '            "end": ' + style.end + ',\n';
                    jsonContent += '            "text": "' + escapeJsonString(style.text) + '",\n';
                    jsonContent += '            "font": "' + escapeJsonString(style.font) + '",\n';
                    jsonContent += '            "style": "' + escapeJsonString(style.style) + '",\n';
                    jsonContent += '            "size": ' + style.size;
                    
                    // Add color if available
                    if (style.color && style.color.length) {
                        jsonContent += ',\n';
                        jsonContent += '            "color": [' + style.color.join(', ') + ']\n';
                    } else {
                        jsonContent += '\n';
                    }
                    
                    jsonContent += '          }' + (j < layer.styleInfo.styles.length - 1 ? ',\n' : '\n');
                }
            }
            
            jsonContent += '        ]\n';
            jsonContent += '      },\n';
            jsonContent += '      "hasRichTextFormatting": ' + (layer.hasRichTextFormatting ? 'true' : 'false') + '\n';
            jsonContent += '    }' + (i < textLayers.length - 1 ? ',\n' : '\n');
        }
        
        jsonContent += '  ]\n';
        jsonContent += '}';
        
        // Write to file
        writeTextFile(saveFile, jsonContent);
        
        // Provide feedback
        var resultMessage = "Extracted " + textLayers.length + " text layers from document \"" + docName + "\".\n\n";
        resultMessage += "Text data saved to: " + saveFile.fsName;
        
        // NEVER show alerts in automation mode - just log to console
        // This ensures no user interaction is required
        $.writeln(resultMessage);
        
        // Explicitly signal to the system that we're done - speeds up detection of completion
        try {
            // Create a simple file to signal completion
            var signalFile = new File(saveFile.path + "/complete_signal.tmp");
            signalFile.open("w");
            signalFile.write("done");
            signalFile.close();
        } catch (e) {
            // Ignore any errors with signaling
        }
        
    } catch (err) {
        // Log errors without showing alert dialogs
        $.writeln("ERROR: " + err.message);
    }
}

// Run the script
main();