<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfsetting requesttimeout="900">
<cfcontent type="application/json; charset=utf-8" reset="true">

<cfset response = structNew()>
<cfset response["OK"] = false>

<cftry>
    <!--- Load API Key --->
    <cfset CLAUDE_API_KEY = "">
    <cfset configPath = getDirectoryFromPath(getCurrentTemplatePath()) & "../../config/claude.json">
    <cfif fileExists(configPath)>
        <cfset configData = deserializeJSON(fileRead(configPath))>
        <cfif structKeyExists(configData, "apiKey")>
            <cfset CLAUDE_API_KEY = configData.apiKey>
        </cfif>
    </cfif>

    <cfif NOT len(CLAUDE_API_KEY)>
        <cfthrow message="Claude API key not configured">
    </cfif>

    <!--- Find uploaded files --->
    <cfset uploadedFiles = arrayNew(1)>
    <cfset formFields = form.keyArray()>
    <cfloop array="#formFields#" index="fieldName">
        <cfif reFindNoCase("^file[0-9]+$", fieldName) AND len(form[fieldName])>
            <cfset arrayAppend(uploadedFiles, fieldName)>
        </cfif>
    </cfloop>

    <cfif arrayLen(uploadedFiles) EQ 0>
        <cfthrow message="No files uploaded">
    </cfif>

    <!--- Upload and prepare all images first --->
    <cfset imageDataArray = arrayNew(1)>
    <cfset uploadDir = getTempDirectory() & "payfrit_menu_uploads/">
    <cfif NOT directoryExists(uploadDir)>
        <cfdirectory action="create" directory="#uploadDir#">
    </cfif>

    <cfloop array="#uploadedFiles#" index="fieldName">
        <cffile action="upload" destination="#uploadDir#" filefield="#fieldName#" accept="image/jpeg,image/png,image/gif,image/webp,application/pdf" nameconflict="makeunique" result="uploadResult">

        <cfif uploadResult.fileWasSaved>
            <cfset filePath = uploadResult.serverDirectory & "/" & uploadResult.serverFile>
            <cfset fileExt = lCase(uploadResult.serverFileExt)>

            <!--- Resize large images --->
            <cfif listFindNoCase("jpg,jpeg,png,gif,webp", fileExt)>
                <cfimage action="read" source="#filePath#" name="img">
                <cfif img.width GT 1600 OR img.height GT 1600>
                    <cfif img.width GT img.height>
                        <cfimage action="resize" source="#img#" width="1600" name="img">
                    <cfelse>
                        <cfimage action="resize" source="#img#" height="1600" name="img">
                    </cfif>
                    <cfimage action="write" source="#img#" destination="#filePath#" quality="0.8" overwrite="true">
                </cfif>
            </cfif>

            <!--- Read and encode --->
            <cffile action="readbinary" file="#filePath#" variable="fileContent">
            <cfset base64Content = toBase64(fileContent)>

            <cfset mediaType = "image/jpeg">
            <cfif fileExt EQ "png"><cfset mediaType = "image/png"></cfif>
            <cfif fileExt EQ "gif"><cfset mediaType = "image/gif"></cfif>
            <cfif fileExt EQ "webp"><cfset mediaType = "image/webp"></cfif>
            <cfif fileExt EQ "pdf"><cfset mediaType = "application/pdf"></cfif>

            <cfset imgSource = structNew()>
            <cfset imgSource["type"] = "base64">
            <cfset imgSource["media_type"] = mediaType>
            <cfset imgSource["data"] = base64Content>

            <cfset imgStruct = structNew()>
            <cfif fileExt EQ "pdf">
                <cfset imgStruct["type"] = "document">
            <cfelse>
                <cfset imgStruct["type"] = "image">
            </cfif>
            <cfset imgStruct["source"] = imgSource>
            <cfset arrayAppend(imageDataArray, imgStruct)>

            <cffile action="delete" file="#filePath#">
        </cfif>
    </cfloop>

    <cfif arrayLen(imageDataArray) EQ 0>
        <cfthrow message="No valid images could be processed">
    </cfif>

    <!--- System prompt for per-image analysis --->
    <cfset systemPrompt = "You are an expert at extracting structured menu data from restaurant menu images. Extract ALL data visible on THIS image only. Return valid JSON with these keys: business (object with name, address, phone, hours, brandColor - only if visible), categories (array of category names visible), modifiers (array of modifier templates with name, required boolean, appliesTo (string: 'category', 'item', or 'uncertain'), categoryName (if appliesTo='category'), and options array where each option is an object with 'name' and 'price' keys), items (array with name, description, price, category, and modifiers array), isHeaderCandidate (boolean - true if this image shows a restaurant interior, exterior, food photography, logo/branding, or banner that would work well as a menu header image). For brandColor: Extract the dominant accent/brand color from the menu design (logo, headers, accent elements). Return as a 6-digit hex code WITHOUT the ## symbol (e.g. ""E74C3C"" for red). Choose a vibrant, appealing color that represents the restaurant's brand. CRITICAL for hours: Extract ALL days' hours including Saturday and Sunday. Format as a single string with ALL days, e.g. ""Mon-Fri 10:30am-10pm, Sat 11am-10pm, Sun 11am-9pm"". If hours are shown as ""Monday - Friday: X"" followed by Saturday and Sunday hours, you MUST include the weekend hours too. Never omit weekend hours if they are visible. For modifier options, ALWAYS use format: {""name"": ""option name"", ""price"": 0}. IMPORTANT: Look for modifiers in these locations: (1) text under category headers (e.g., 'All burgers include...', 'Choose your size:'), (2) item descriptions (e.g., 'served with choice of side'), (3) asterisk notes, (4) header/footer text. For modifiers found under category headers, set appliesTo='category' and categoryName to the category. For modifiers clearly in item descriptions, assign them to that item's modifiers array. For modifiers where you're uncertain how they apply, set appliesTo='uncertain' and do NOT assign to items. Return ONLY valid JSON, no markdown, no explanation.">

    <!--- Process each image individually --->
    <cfset allResults = arrayNew(1)>

    <cfloop from="1" to="#arrayLen(imageDataArray)#" index="imgIndex">
        <cfset imgData = imageDataArray[imgIndex]>

        <!--- Build message for this single image --->
        <cfset messagesContent = arrayNew(1)>
        <cfset arrayAppend(messagesContent, imgData)>

        <cfset textBlock = structNew()>
        <cfset textBlock["type"] = "text">
        <cfset textBlock["text"] = "Extract all menu data from this image. Return JSON with: business (if visible), categories, modifiers (with appliesTo, categoryName if applicable, and options as objects with name and price keys), items (with modifiers array only for item-specific modifiers). Look for modifiers under category headers, in item descriptions, in notes/asterisks, or in headers/footers. Example category-level modifier: {""name"": ""Size"", ""required"": true, ""appliesTo"": ""category"", ""categoryName"": ""Burgers"", ""options"": [{""name"": ""Small"", ""price"": 0}, {""name"": ""Large"", ""price"": 1.50}]}. Example uncertain modifier: {""name"": ""Toppings"", ""required"": false, ""appliesTo"": ""uncertain"", ""options"": [{""name"": ""Lettuce"", ""price"": 0}, {""name"": ""Tomato"", ""price"": 0}]}. Only assign modifiers to item.modifiers array when the modifier is clearly specific to that individual item from its description.">
        <cfset arrayAppend(messagesContent, textBlock)>

        <cfset userMessage = structNew()>
        <cfset userMessage["role"] = "user">
        <cfset userMessage["content"] = messagesContent>

        <cfset requestBody = structNew()>
        <cfset requestBody["model"] = "claude-sonnet-4-20250514">
        <cfset requestBody["max_tokens"] = 8192>
        <cfset requestBody["temperature"] = 0>
        <cfset requestBody["system"] = systemPrompt>
        <cfset requestBody["messages"] = arrayNew(1)>
        <cfset arrayAppend(requestBody["messages"], userMessage)>

        <!--- Call Claude API for this image --->
        <cfhttp url="https://api.anthropic.com/v1/messages" method="POST" timeout="120" result="httpResult">
            <cfhttpparam type="header" name="Content-Type" value="application/json">
            <cfhttpparam type="header" name="x-api-key" value="#CLAUDE_API_KEY#">
            <cfhttpparam type="header" name="anthropic-version" value="2023-06-01">
            <cfhttpparam type="body" value="#serializeJSON(requestBody)#">
        </cfhttp>

        <cfset httpStatusCode = httpResult.statusCode>
        <cfif isNumeric(httpStatusCode)>
            <cfset httpStatusCode = int(httpStatusCode)>
        <cfelseif findNoCase("200", httpStatusCode)>
            <cfset httpStatusCode = 200>
        <cfelse>
            <cfset httpStatusCode = 0>
        </cfif>

        <cfif httpStatusCode NEQ 200>
            <cfset errorDetail = "">
            <cftry>
                <cfset errorResponse = deserializeJSON(httpResult.fileContent)>
                <cfif structKeyExists(errorResponse, "error") AND structKeyExists(errorResponse.error, "message")>
                    <cfset errorDetail = errorResponse.error.message>
                <cfelse>
                    <cfset errorDetail = httpResult.fileContent>
                </cfif>
                <cfcatch>
                    <cfset errorDetail = httpResult.fileContent>
                </cfcatch>
            </cftry>
            <cfthrow message="Claude API error on image #imgIndex#: #httpResult.statusCode# - #errorDetail#">
        </cfif>

        <!--- Parse response --->
        <cfset claudeResponse = deserializeJSON(httpResult.fileContent)>
        <cfif NOT structKeyExists(claudeResponse, "content") OR NOT arrayLen(claudeResponse.content)>
            <cfthrow message="Empty response from Claude for image #imgIndex#">
        </cfif>

        <cfset responseText = "">
        <cfloop array="#claudeResponse.content#" index="block">
            <cfif structKeyExists(block, "type") AND block.type EQ "text">
                <cfset responseText = block.text>
                <cfbreak>
            </cfif>
        </cfloop>

        <!--- Clean up JSON response --->
        <cfset responseText = trim(responseText)>
        <cfif left(responseText, 7) EQ "```json">
            <cfset responseText = mid(responseText, 8, len(responseText) - 7)>
        </cfif>
        <cfif left(responseText, 3) EQ "```">
            <cfset responseText = mid(responseText, 4, len(responseText) - 3)>
        </cfif>
        <cfif right(responseText, 3) EQ "```">
            <cfset responseText = left(responseText, len(responseText) - 3)>
        </cfif>
        <cfset responseText = trim(responseText)>
        <cfset responseText = reReplace(responseText, ",(\s*[\]\}])", "\1", "all")>

        <cfset imageResult = deserializeJSON(responseText)>
        <cfset arrayAppend(allResults, imageResult)>
    </cfloop>

    <!--- MERGE PHASE: Combine all results --->

    <!--- Track header candidates (image indices that would work as headers) --->
    <cfset headerCandidateIndices = arrayNew(1)>
    <cfloop from="1" to="#arrayLen(allResults)#" index="resultIdx">
        <cfset result = allResults[resultIdx]>
        <cfif structKeyExists(result, "isHeaderCandidate") AND result.isHeaderCandidate EQ true>
            <cfset arrayAppend(headerCandidateIndices, resultIdx - 1)><!--- 0-indexed for JS --->
        </cfif>
    </cfloop>

    <!--- 1. Extract business info (last image wins so user controls via upload order) --->
    <cfset mergedBusiness = structNew()>
    <cfset bizFields = "name,address,addressLine1,city,state,zip,phone,hours,brandColor">
    <cfloop array="#allResults#" index="result">
        <cfif structKeyExists(result, "business") AND isStruct(result.business)>
            <cfloop list="#bizFields#" index="fieldName">
                <cfif structKeyExists(result.business, fieldName)>
                    <cfset fieldVal = result.business[fieldName]>
                    <cfif isSimpleValue(fieldVal) AND len(trim(fieldVal))>
                        <cfset mergedBusiness[fieldName] = trim(fieldVal)>
                    <cfelseif isStruct(fieldVal)>
                        <!--- Convert struct to readable string (e.g. hours as key-value pairs) --->
                        <cfset parts = arrayNew(1)>
                        <cfloop collection="#fieldVal#" item="k">
                            <cfif isSimpleValue(fieldVal[k])>
                                <cfset arrayAppend(parts, k & ": " & fieldVal[k])>
                            </cfif>
                        </cfloop>
                        <cfif arrayLen(parts)>
                            <cfset mergedBusiness[fieldName] = arrayToList(parts, ", ")>
                        </cfif>
                    <cfelseif isArray(fieldVal) AND arrayLen(fieldVal)>
                        <!--- Convert array to readable string --->
                        <cfset parts = arrayNew(1)>
                        <cfloop array="#fieldVal#" index="entry">
                            <cfif isSimpleValue(entry) AND len(trim(entry))>
                                <cfset arrayAppend(parts, trim(entry))>
                            <cfelseif isStruct(entry)>
                                <!--- e.g. {day:"Mon", hours:"11am-9pm"} --->
                                <cfset entryParts = arrayNew(1)>
                                <cfloop collection="#entry#" item="k">
                                    <cfif isSimpleValue(entry[k])>
                                        <cfset arrayAppend(entryParts, entry[k])>
                                    </cfif>
                                </cfloop>
                                <cfif arrayLen(entryParts)>
                                    <cfset arrayAppend(parts, arrayToList(entryParts, " "))>
                                </cfif>
                            </cfif>
                        </cfloop>
                        <cfif arrayLen(parts)>
                            <cfset mergedBusiness[fieldName] = arrayToList(parts, ", ")>
                        </cfif>
                    </cfif>
                </cfif>
            </cfloop>
        </cfif>
    </cfloop>

    <!--- 2. Merge categories (dedupe by name) --->
    <cfset categoryMap = structNew()>
    <cfloop array="#allResults#" index="result">
        <cfif structKeyExists(result, "categories") AND isArray(result.categories)>
            <cfloop array="#result.categories#" index="cat">
                <cfset catName = "">
                <cfif isSimpleValue(cat) AND len(trim(cat))>
                    <cfset catName = trim(cat)>
                <cfelseif isStruct(cat)>
                    <!--- Try common key names --->
                    <cfif structKeyExists(cat, "name") AND len(trim(cat.name))>
                        <cfset catName = trim(cat.name)>
                    <cfelseif structKeyExists(cat, "category") AND len(trim(cat.category))>
                        <cfset catName = trim(cat.category)>
                    <cfelseif structKeyExists(cat, "title") AND len(trim(cat.title))>
                        <cfset catName = trim(cat.title)>
                    </cfif>
                </cfif>
                <cfif len(catName)>
                    <cfset categoryMap[lCase(catName)] = catName>
                </cfif>
            </cfloop>
        </cfif>
    </cfloop>
    <cfset mergedCategories = arrayNew(1)>
    <cfloop collection="#categoryMap#" item="catKey">
        <cfset catObj = structNew()>
        <cfset catObj["name"] = categoryMap[catKey]>
        <cfset catObj["itemCount"] = 0>
        <cfset arrayAppend(mergedCategories, catObj)>
    </cfloop>

    <!--- 3. Merge modifiers (dedupe by name) --->
    <cfset modifierMap = structNew()>
    <cfloop from="1" to="#arrayLen(allResults)#" index="resultIndex">
        <cfset result = allResults[resultIndex]>
        <cfif structKeyExists(result, "modifiers") AND isArray(result.modifiers)>
            <cfloop array="#result.modifiers#" index="mod">
                <cfif isStruct(mod) AND structKeyExists(mod, "name") AND len(trim(mod.name))>
                    <cfset modKey = lCase(mod.name)>
                    <cfif NOT structKeyExists(modifierMap, modKey)>
                        <!--- Normalize the modifier structure --->
                        <cfset normalizedMod = structNew()>
                        <cfset normalizedMod["name"] = trim(mod.name)>
                        <cfset normalizedMod["required"] = structKeyExists(mod, "required") AND mod.required EQ true>
                        <cfset normalizedMod["appliesTo"] = structKeyExists(mod, "appliesTo") ? mod.appliesTo : "uncertain">
                        <cfset normalizedMod["sourceImageIndex"] = resultIndex>
                        <cfif structKeyExists(mod, "categoryName") AND len(trim(mod.categoryName))>
                            <cfset normalizedMod["categoryName"] = trim(mod.categoryName)>
                        </cfif>
                        <cfset normalizedMod["options"] = arrayNew(1)>

                        <!--- Normalize options array --->
                        <cfif structKeyExists(mod, "options") AND isArray(mod.options)>
                            <cfloop array="#mod.options#" index="opt">
                                <cfset normalizedOpt = structNew()>
                                <cfif isSimpleValue(opt) AND len(trim(opt))>
                                    <!--- Option is just a string --->
                                    <cfset normalizedOpt["name"] = trim(opt)>
                                    <cfset normalizedOpt["price"] = 0>
                                <cfelseif isStruct(opt)>
                                    <!--- Option is an object - try different key names --->
                                    <cfset optName = "">
                                    <cfif structKeyExists(opt, "name") AND len(trim(opt.name))>
                                        <cfset optName = trim(opt.name)>
                                    <cfelseif structKeyExists(opt, "option") AND len(trim(opt.option))>
                                        <cfset optName = trim(opt.option)>
                                    <cfelseif structKeyExists(opt, "label") AND len(trim(opt.label))>
                                        <cfset optName = trim(opt.label)>
                                    </cfif>
                                    <cfif len(optName)>
                                        <cfset normalizedOpt["name"] = optName>
                                        <cfset normalizedOpt["price"] = 0>
                                        <cfif structKeyExists(opt, "price")>
                                            <cfif isNumeric(opt.price)>
                                                <cfset normalizedOpt["price"] = opt.price>
                                            <cfelseif isSimpleValue(opt.price)>
                                                <!--- Try to parse price string like "$1.50" or "+$1.50" --->
                                                <cfset priceStr = reReplace(opt.price, "[^0-9.]", "", "all")>
                                                <cfif isNumeric(priceStr)>
                                                    <cfset normalizedOpt["price"] = val(priceStr)>
                                                </cfif>
                                            </cfif>
                                        </cfif>
                                    </cfif>
                                </cfif>
                                <cfif structKeyExists(normalizedOpt, "name") AND len(normalizedOpt.name)>
                                    <cfset arrayAppend(normalizedMod["options"], normalizedOpt)>
                                </cfif>
                            </cfloop>
                        </cfif>

                        <!--- Only add modifier if it has at least one valid option --->
                        <cfif arrayLen(normalizedMod["options"]) GT 0>
                            <cfset modifierMap[modKey] = normalizedMod>
                        </cfif>
                    </cfif>
                </cfif>
            </cfloop>
        </cfif>
    </cfloop>
    <cfset mergedModifiers = arrayNew(1)>
    <cfloop collection="#modifierMap#" item="modKey">
        <cfset arrayAppend(mergedModifiers, modifierMap[modKey])>
    </cfloop>

    <!--- 4. Merge items (collect all, assign IDs) --->
    <cfset mergedItems = arrayNew(1)>
    <cfset itemIndex = 0>
    <cfloop array="#allResults#" index="result">
        <cfif structKeyExists(result, "items") AND isArray(result.items)>
            <cfloop array="#result.items#" index="item">
                <cfset itemIndex = itemIndex + 1>
                <cfset item["id"] = "item_" & itemIndex>
                <cfset arrayAppend(mergedItems, item)>
            </cfloop>
        </cfif>
    </cfloop>

    <!--- 5. Auto-assign category-level modifiers to items --->
    <cfloop array="#mergedItems#" index="item">
        <!--- Ensure item has modifiers array --->
        <cfif NOT structKeyExists(item, "modifiers") OR NOT isArray(item.modifiers)>
            <cfset item["modifiers"] = arrayNew(1)>
        </cfif>

        <!--- Get item's category --->
        <cfset itemCategory = structKeyExists(item, "category") ? trim(item.category) : "">

        <!--- Find category-level modifiers for this category --->
        <cfif len(itemCategory)>
            <cfloop array="#mergedModifiers#" index="mod">
                <cfif structKeyExists(mod, "appliesTo") AND mod.appliesTo EQ "category" AND structKeyExists(mod, "categoryName")>
                    <!--- Case-insensitive category match --->
                    <cfif lCase(mod.categoryName) EQ lCase(itemCategory)>
                        <!--- Check if modifier is not already assigned --->
                        <cfset alreadyAssigned = false>
                        <cfloop array="#item.modifiers#" index="existingMod">
                            <cfif lCase(existingMod) EQ lCase(mod.name)>
                                <cfset alreadyAssigned = true>
                                <cfbreak>
                            </cfif>
                        </cfloop>

                        <cfif NOT alreadyAssigned>
                            <cfset arrayAppend(item.modifiers, mod.name)>
                        </cfif>
                    </cfif>
                </cfif>
            </cfloop>
        </cfif>
    </cfloop>

    <!--- Build final response --->
    <cfset finalData = structNew()>
    <cfset finalData["business"] = mergedBusiness>
    <cfset finalData["categories"] = mergedCategories>
    <cfset finalData["modifiers"] = mergedModifiers>
    <cfset finalData["items"] = mergedItems>
    <cfset finalData["headerCandidateIndices"] = headerCandidateIndices>

    <cfset response["OK"] = true>
    <cfset response["DATA"] = finalData>
    <cfset response["imagesProcessed"] = arrayLen(imageDataArray)>
    <cfset response["DEBUG_RAW_RESULTS"] = allResults>
    <!--- Debug: show first category structure --->
    <cfif arrayLen(allResults) GT 0 AND structKeyExists(allResults[1], "categories") AND isArray(allResults[1].categories) AND arrayLen(allResults[1].categories) GT 0>
        <cfset response["DEBUG_FIRST_CAT"] = allResults[1].categories[1]>
        <cfset response["DEBUG_FIRST_CAT_KEYS"] = isStruct(allResults[1].categories[1]) ? structKeyList(allResults[1].categories[1]) : "NOT_A_STRUCT">
    </cfif>
    <!--- Debug: show first item structure --->
    <cfif arrayLen(allResults) GT 0 AND structKeyExists(allResults[1], "items") AND isArray(allResults[1].items) AND arrayLen(allResults[1].items) GT 0>
        <cfset response["DEBUG_FIRST_ITEM"] = allResults[1].items[1]>
    </cfif>
    <!--- Debug: show first modifier from raw results --->
    <cfif arrayLen(allResults) GT 0 AND structKeyExists(allResults[1], "modifiers") AND isArray(allResults[1].modifiers) AND arrayLen(allResults[1].modifiers) GT 0>
        <cfset response["DEBUG_FIRST_RAW_MODIFIER"] = allResults[1].modifiers[1]>
    </cfif>
    <!--- Debug: show first merged modifier --->
    <cfif arrayLen(mergedModifiers) GT 0>
        <cfset response["DEBUG_FIRST_MERGED_MODIFIER"] = mergedModifiers[1]>
    </cfif>

<cfcatch type="any">
    <cfset response["MESSAGE"] = cfcatch.message>
    <cfif len(cfcatch.detail)>
        <cfset response["DETAIL"] = cfcatch.detail>
    </cfif>
    <cfif structKeyExists(cfcatch, "tagContext") AND arrayLen(cfcatch.tagContext) GT 0>
        <cfset response["DEBUG_LINE"] = cfcatch.tagContext[1].line>
        <cfset response["DEBUG_TEMPLATE"] = cfcatch.tagContext[1].template>
    </cfif>
</cfcatch>
</cftry>

<cfoutput>#serializeJSON(response)#</cfoutput>
