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

<cfscript>
/**
 * Save Wizard Data
 *
 * Takes the extracted menu data from the setup wizard and saves it to the database.
 * This transforms the wizard format into the format expected by the import system.
 *
 * POST JSON:
 * {
 *   "businessId": "existing-business-id",
 *   "data": {
 *     "business": { "name": "...", "address": "...", "phone": "...", "hours": "..." },
 *     "categories": [ { "name": "...", "itemCount": 0 } ],
 *     "modifiers": [ { "name": "...", "required": true, "options": [...] } ],
 *     "items": [ { "name": "...", "price": 0, "category": "...", "modifiers": [...] } ]
 *   }
 * }
 */

response = { "OK": false, "steps": [], "errors": [] };

try {
    requestBody = toString(getHttpRequestData().content);
    if (!len(requestBody)) {
        throw(message="No request body provided");
    }

    data = deserializeJSON(requestBody);

    businessId = structKeyExists(data, "businessId") ? val(data.businessId) : 0;
    userId = structKeyExists(data, "userId") ? val(data.userId) : 0;
    providedMenuId = structKeyExists(data, "menuId") ? val(data.menuId) : 0;
    wizardData = structKeyExists(data, "data") ? data.data : {};
    biz = structKeyExists(wizardData, "business") ? wizardData.business : {};

    // If no businessId, create a new business
    if (businessId == 0) {
        response.steps.append("No businessId provided - creating new business");

        bizName = structKeyExists(biz, "name") && isSimpleValue(biz.name) ? biz.name : "";
        if (!len(bizName)) {
            throw(message="Business name is required to create new business");
        }

        if (userId == 0) {
            throw(message="userId is required to create new business");
        }

        // Extract phone number
        bizPhone = structKeyExists(biz, "phone") && isSimpleValue(biz.phone) ? trim(biz.phone) : "";

        // Extract tax rate (stored as decimal, e.g. 8.25% -> 0.0825)
        bizTaxRate = structKeyExists(biz, "taxRatePercent") && isSimpleValue(biz.taxRatePercent) ? val(biz.taxRatePercent) / 100 : 0;

        // Create address record first (use extracted address fields) - safely extract as simple values
        addressLine1 = structKeyExists(biz, "addressLine1") && isSimpleValue(biz.addressLine1) ? trim(biz.addressLine1) : "";
        city = structKeyExists(biz, "city") && isSimpleValue(biz.city) ? trim(biz.city) : "";
        state = structKeyExists(biz, "state") && isSimpleValue(biz.state) ? trim(biz.state) : "";
        zip = structKeyExists(biz, "zip") && isSimpleValue(biz.zip) ? trim(biz.zip) : "";

        // Clean up city - remove trailing punctuation (commas, periods, etc.)
        city = reReplace(city, "[,.\s]+$", "", "all");

        // Look up state ID from state abbreviation
        stateID = 0;
        response.steps.append("State value received: '" & state & "' (len: " & len(state) & ")");
        if (len(state)) {
            qState = queryTimed("
                SELECT ID FROM tt_States WHERE Abbreviation = :abbr
            ", { abbr: uCase(state) }, { datasource: "payfrit" });

            response.steps.append("State lookup for '" & uCase(state) & "' found " & qState.recordCount & " records");
            if (qState.recordCount > 0) {
                stateID = qState.ID;
                response.steps.append("Using stateID: " & stateID);
            }
        }

        queryTimed("
            INSERT INTO Addresses (Line1, City, StateID, ZIPCode, UserID, AddressTypeID, AddedOn)
            VALUES (:line1, :city, :stateID, :zip, :userID, :typeID, NOW())
        ", {
            line1: len(addressLine1) ? addressLine1 : "Address pending",
            city: len(city) ? city : "",
            stateID: { value = stateID > 0 ? stateID : javaCast("null", ""), cfsqltype = "cf_sql_integer", null = stateID == 0 },
            zip: len(zip) ? zip : "",
            userID: userId,
            typeID: 2
        }, { datasource: "payfrit" });

        qNewAddr = queryTimed("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
        addressId = qNewAddr.id;
        response.steps.append("Created address record (ID: " & addressId & ")");

        // Get community meal type (1=provide meals, 2=food bank donation)
        communityMealType = structKeyExists(wizardData, "communityMealType") && isSimpleValue(wizardData.communityMealType) ? val(wizardData.communityMealType) : 1;
        if (communityMealType < 1 || communityMealType > 2) communityMealType = 1;

        // Create new business with address link and phone
        queryTimed("
            INSERT INTO Businesses (Name, Phone, UserID, AddressID, DeliveryZIPCodes, BusinessCommunityMealType, TaxRate, BusinessAddedOn)
            VALUES (:name, :phone, :userId, :addressId, :deliveryZips, :communityMealType, :taxRate, NOW())
        ", {
            name: bizName,
            phone: bizPhone,
            userId: userId,
            addressId: addressId,
            deliveryZips: len(zip) ? zip : "",
            communityMealType: communityMealType,
            taxRate: { value: bizTaxRate, cfsqltype: "cf_sql_decimal" }
        }, { datasource: "payfrit" });

        qNewBiz = queryTimed("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
        businessId = qNewBiz.id;
        response.steps.append("Created new business: " & bizName & " (ID: " & businessId & ")");

        // Update address with business ID link
        queryTimed("
            UPDATE Addresses SET BusinessID = :businessID WHERE ID = :addressID
        ", {
            businessID: businessId,
            addressID: addressId
        }, { datasource: "payfrit" });
        response.steps.append("Linked address to business");

        // Create default task types for the business
        // 1. Call Staff (notifications icon, purple)
        // 2. Chat With Staff (chat icon, blue)
        // 3. Pay With Cash (payments icon, green)
        defaultTaskTypes = [
            { name: "Call Staff", icon: "notifications", color: "##9C27B0", description: "Request staff assistance" },
            { name: "Chat With Staff", icon: "chat", color: "##2196F3", description: "Open a chat conversation" },
            { name: "Pay With Cash", icon: "payments", color: "##4CAF50", description: "Request to pay with cash" }
        ];

        for (tt = 1; tt <= arrayLen(defaultTaskTypes); tt++) {
            taskType = defaultTaskTypes[tt];
            queryTimed("
                INSERT INTO tt_TaskTypes (Name, Description, Icon, Color, BusinessID, SortOrder)
                VALUES (:name, :description, :icon, :color, :businessID, :sortOrder)
            ", {
                name: { value: taskType.name, cfsqltype: "cf_sql_varchar" },
                description: { value: taskType.description, cfsqltype: "cf_sql_varchar" },
                icon: { value: taskType.icon, cfsqltype: "cf_sql_varchar" },
                color: { value: taskType.color, cfsqltype: "cf_sql_varchar" },
                businessID: { value: businessId, cfsqltype: "cf_sql_integer" },
                sortOrder: { value: tt, cfsqltype: "cf_sql_integer" }
            }, { datasource: "payfrit" });
        }
        response.steps.append("Created 3 default task types (Call Staff, Chat With Staff, Pay With Cash)");

        // Create default task categories for the business
        defaultTaskCategories = [
            { name: "Service Point", color: "##F44336" },  // Red
            { name: "Kitchen", color: "##FF9800" },        // Orange
            { name: "Bar", color: "##9C27B0" },            // Purple
            { name: "Cleaning", color: "##4CAF50" },       // Green
            { name: "Management", color: "##2196F3" },     // Blue
            { name: "Delivery", color: "##00BCD4" },       // Cyan
            { name: "General", color: "##607D8B" }         // Blue Grey
        ];

        for (tc = 1; tc <= arrayLen(defaultTaskCategories); tc++) {
            taskCat = defaultTaskCategories[tc];
            queryTimed("
                INSERT INTO TaskCategories (BusinessID, Name, Color)
                VALUES (:businessID, :name, :color)
            ", {
                businessID: { value: businessId, cfsqltype: "cf_sql_integer" },
                name: { value: taskCat.name, cfsqltype: "cf_sql_varchar" },
                color: { value: taskCat.color, cfsqltype: "cf_sql_varchar" }
            }, { datasource: "payfrit" });
        }
        response.steps.append("Created 7 default task categories");

        // Save business hours from structured schedule
        if (structKeyExists(biz, "hoursSchedule") && isArray(biz.hoursSchedule)) {
            hoursSchedule = biz.hoursSchedule;
            response.steps.append("Processing " & arrayLen(hoursSchedule) & " days of hours");

            for (i = 1; i <= arrayLen(hoursSchedule); i++) {
                dayData = hoursSchedule[i];
                if (!isStruct(dayData)) continue;

                dayID = structKeyExists(dayData, "dayId") ? val(dayData.dayId) : 0;
                openTime = structKeyExists(dayData, "open") && isSimpleValue(dayData.open) ? dayData.open : "09:00";
                closeTime = structKeyExists(dayData, "close") && isSimpleValue(dayData.close) ? dayData.close : "17:00";

                // Convert HH:MM to HH:MM:SS if needed
                if (len(openTime) == 5) openTime = openTime & ":00";
                if (len(closeTime) == 5) closeTime = closeTime & ":00";

                // Insert hours record for this day
                queryTimed("
                    INSERT INTO Hours (BusinessID, DayID, OpenTime, ClosingTime)
                    VALUES (:bizID, :dayID, :openTime, :closeTime)
                ", {
                    bizID: businessId,
                    dayID: dayID,
                    openTime: openTime,
                    closeTime: closeTime
                }, { datasource: "payfrit" });
            }

            response.steps.append("Created " & arrayLen(hoursSchedule) & " hours records");
        }
    } else {
        // Verify existing business exists
        qBiz = queryTimed("
            SELECT ID, Name AS BusinessName FROM Businesses WHERE ID = :id
        ", { id: businessId }, { datasource: "payfrit" });

        if (qBiz.recordCount == 0) {
            throw(message="Business not found: " & businessId);
        }

        response.steps.append("Found existing business: " & qBiz.BusinessName);
    }

    // Build modifier template map
    // The wizard format has modifiers as simple objects, we need to create IDs
    modTemplates = structKeyExists(wizardData, "modifiers") ? wizardData.modifiers : [];
    templateMap = {}; // Maps modifier name to database ItemID

    response.steps.append("Processing " & arrayLen(modTemplates) & " modifier templates...");

    for (i = 1; i <= arrayLen(modTemplates); i++) {
        tmpl = modTemplates[i];
        tmplName = structKeyExists(tmpl, "name") && isSimpleValue(tmpl.name) ? tmpl.name : "";
        if (!len(tmplName)) {
            response.steps.append("Warning: Skipping modifier template with no name at index " & i);
            continue;
        }
        required = structKeyExists(tmpl, "required") && tmpl.required == true;
        options = structKeyExists(tmpl, "options") && isArray(tmpl.options) ? tmpl.options : [];

        // Debug: Log options info
        response.steps.append("Template '" & tmplName & "' has " & arrayLen(options) & " options (type: " & (isArray(options) ? "array" : "other") & ")");

        // Check if template already exists for this business
        qTmpl = queryTimed("
            SELECT i.ID FROM Items i
            WHERE i.BusinessID = :bizID
              AND i.Name = :name
              AND i.ParentItemID = 0
              AND i.CategoryID = 0
        ", { bizID: businessId, name: tmplName }, { datasource: "payfrit" });

        if (qTmpl.recordCount > 0) {
            templateItemID = qTmpl.ID;
            response.steps.append("Template exists: " & tmplName & " (ID: " & templateItemID & ")");
        } else {
            // Create template as Item with CategoryID=0 to mark as template
            queryTimed("
                INSERT INTO Items (
                    BusinessID, Name, ParentItemID, CategoryID, Price,
                    IsActive, RequiresChildSelection, MaxNumSelectionReq,
                    SortOrder
                ) VALUES (
                    :bizID, :name, 0, 0, 0, 1, :required, 1, 0
                )
            ", {
                bizID: businessId,
                name: tmplName,
                required: required ? 1 : 0
            }, { datasource: "payfrit" });

            qNewTmpl = queryTimed("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
            templateItemID = qNewTmpl.id;
            response.steps.append("Created template: " & tmplName & " (ID: " & templateItemID & ")");
        }

        templateMap[tmplName] = templateItemID;

        // Create/update template options
        optionOrder = 1;
        for (j = 1; j <= arrayLen(options); j++) {
            opt = options[j];
            // Safety check: ensure opt is a struct with a name that's a simple value
            if (!isStruct(opt)) continue;
            if (!structKeyExists(opt, "name")) continue;
            if (!isSimpleValue(opt.name)) continue;
            if (!len(opt.name)) continue;

            optName = opt.name;
            optPrice = structKeyExists(opt, "price") && isSimpleValue(opt.price) ? val(opt.price) : 0;

            qOpt = queryTimed("
                SELECT ID FROM Items
                WHERE BusinessID = :bizID AND Name = :name AND ParentItemID = :parentID
            ", { bizID: businessId, name: optName, parentID: templateItemID }, { datasource: "payfrit" });

            if (qOpt.recordCount == 0) {
                queryTimed("
                    INSERT INTO Items (
                        BusinessID, Name, ParentItemID, CategoryID,
                        Price, IsActive, SortOrder
                    ) VALUES (
                        :bizID, :name, :parentID, 0, :price, 1, :sortOrder
                    )
                ", {
                    bizID: businessId,
                    name: optName,
                    parentID: templateItemID,
                    price: optPrice,
                    sortOrder: optionOrder
                }, { datasource: "payfrit" });
            }
            optionOrder++;
        }
    }

    // Use provided menuId (add-menu mode) or create/find menu
    if (providedMenuId > 0) {
        menuID = providedMenuId;
        // Look up the menu name for logging
        qName = queryTimed("SELECT Name FROM Menus WHERE ID = :id", { id: menuID }, { datasource: "payfrit" });
        menuName = qName.recordCount > 0 ? qName.Name : "Menu " & menuID;
        response.steps.append("Using provided menu: " & menuName & " (ID: " & menuID & ")");
    } else {
        // Create a Menu record for this business (or get existing menu with same name)
        menuName = structKeyExists(wizardData, "menuName") && isSimpleValue(wizardData.menuName) && len(trim(wizardData.menuName))
            ? trim(wizardData.menuName)
            : "Main Menu";

        // Get menu time range (optional)
        menuStartTime = structKeyExists(wizardData, "menuStartTime") && isSimpleValue(wizardData.menuStartTime) && len(trim(wizardData.menuStartTime))
            ? trim(wizardData.menuStartTime)
            : "";
        menuEndTime = structKeyExists(wizardData, "menuEndTime") && isSimpleValue(wizardData.menuEndTime) && len(trim(wizardData.menuEndTime))
            ? trim(wizardData.menuEndTime)
            : "";

        // Convert HH:MM to HH:MM:SS if needed
        if (len(menuStartTime) == 5) menuStartTime = menuStartTime & ":00";
        if (len(menuEndTime) == 5) menuEndTime = menuEndTime & ":00";

        // Validate menu hours fall within business operating hours
        if (len(menuStartTime) && len(menuEndTime)) {
            qHours = queryTimed("
                SELECT MIN(OpenTime) as earliestOpen, MAX(ClosingTime) as latestClose
                FROM Hours
                WHERE BusinessID = :bizID
            ", { bizID: businessId }, { datasource: "payfrit" });

            if (qHours.recordCount > 0 && !isNull(qHours.earliestOpen) && !isNull(qHours.latestClose)) {
                earliestOpen = timeFormat(qHours.earliestOpen, "HH:mm:ss");
                latestClose = timeFormat(qHours.latestClose, "HH:mm:ss");

                if (menuStartTime < earliestOpen || menuEndTime > latestClose) {
                    throw(message="Menu hours (" & menuStartTime & " - " & menuEndTime & ") must be within business operating hours (" & earliestOpen & " - " & latestClose & ")");
                }
                response.steps.append("Validated menu hours against business hours (" & earliestOpen & " - " & latestClose & ")");
            }
        }

        qMenu = queryTimed("
            SELECT ID FROM Menus
            WHERE BusinessID = :bizID AND Name = :name AND IsActive = 1
        ", { bizID: businessId, name: menuName }, { datasource: "payfrit" });

        if (qMenu.recordCount > 0) {
            menuID = qMenu.ID;
            // Update existing menu with new time range if provided
            if (len(menuStartTime) && len(menuEndTime)) {
                queryTimed("
                    UPDATE Menus SET StartTime = :startTime, EndTime = :endTime
                    WHERE ID = :menuID
                ", {
                    menuID: menuID,
                    startTime: menuStartTime,
                    endTime: menuEndTime
                }, { datasource: "payfrit" });
                response.steps.append("Updated existing menu: " & menuName & " (ID: " & menuID & ") with hours " & menuStartTime & " - " & menuEndTime);
            } else {
                response.steps.append("Using existing menu: " & menuName & " (ID: " & menuID & ")");
            }
        } else {
            queryTimed("
                INSERT INTO Menus (
                    BusinessID, Name, DaysActive, StartTime, EndTime, SortOrder, IsActive, AddedOn
                ) VALUES (
                    :bizID, :name, 127, :startTime, :endTime, 0, 1, NOW()
                )
            ", {
                bizID: businessId,
                name: menuName,
                startTime: len(menuStartTime) ? menuStartTime : javaCast("null", ""),
                endTime: len(menuEndTime) ? menuEndTime : javaCast("null", "")
            }, { datasource: "payfrit" });

            qNewMenu = queryTimed("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
            menuID = qNewMenu.id;
            timeInfo = len(menuStartTime) && len(menuEndTime) ? " (" & menuStartTime & " - " & menuEndTime & ")" : " (all day)";
            response.steps.append("Created menu: " & menuName & timeInfo & " (ID: " & menuID & ")");
        }
    }

    // Build category map
    categories = structKeyExists(wizardData, "categories") ? wizardData.categories : [];
    categoryMap = {}; // Maps category name to CategoryID

    response.steps.append("Processing " & arrayLen(categories) & " categories...");

    catOrder = 1;
    for (c = 1; c <= arrayLen(categories); c++) {
        cat = categories[c];
        catName = structKeyExists(cat, "name") && isSimpleValue(cat.name) ? cat.name : "";
        if (!len(catName)) {
            response.steps.append("Warning: Skipping category with no name at index " & c);
            continue;
        }

        // Check if category exists in Categories table for this menu
        qCat = queryTimed("
            SELECT ID FROM Categories
            WHERE BusinessID = :bizID AND Name = :name AND MenuID = :menuID
        ", { bizID: businessId, name: catName, menuID: menuID }, { datasource: "payfrit" });

        if (qCat.recordCount > 0) {
            categoryID = qCat.ID;
            response.steps.append("Category exists: " & catName & " (ID: " & categoryID & ")");
        } else {
            // Create category in Categories table with MenuID
            queryTimed("
                INSERT INTO Categories (
                    BusinessID, MenuID, Name, SortOrder
                ) VALUES (
                    :bizID, :menuID, :name, :sortOrder
                )
            ", {
                bizID: businessId,
                menuID: menuID,
                name: catName,
                sortOrder: catOrder
            }, { datasource: "payfrit" });

            qNewCat = queryTimed("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
            categoryID = qNewCat.id;
            response.steps.append("Created category: " & catName & " in menu " & menuName & " (ID: " & categoryID & ")");
        }

        categoryMap[catName] = categoryID;
        catOrder++;
    }

    // Create menu items
    items = structKeyExists(wizardData, "items") ? wizardData.items : [];

    response.steps.append("Processing " & arrayLen(items) & " menu items...");

    totalItems = 0;
    totalLinks = 0;

    // Track item order within each category
    categoryItemOrder = {};

    for (n = 1; n <= arrayLen(items); n++) {
        item = items[n];
        if (!isStruct(item)) continue;

        // Safely extract all item fields - ensure they're simple values
        itemName = structKeyExists(item, "name") && isSimpleValue(item.name) ? item.name : "";
        if (!len(itemName)) {
            response.steps.append("Warning: Skipping item with no name at index " & n);
            continue;
        }

        itemDesc = "";
        if (structKeyExists(item, "description") && isSimpleValue(item.description)) {
            itemDesc = item.description;
        }

        itemPrice = 0;
        if (structKeyExists(item, "price")) {
            if (isSimpleValue(item.price)) {
                itemPrice = val(item.price);
            }
        }

        itemCategory = "";
        if (structKeyExists(item, "category") && isSimpleValue(item.category)) {
            itemCategory = item.category;
        }

        itemModifiers = structKeyExists(item, "modifiers") && isArray(item.modifiers) ? item.modifiers : [];

        // Get category ID
        if (!len(itemCategory) || !structKeyExists(categoryMap, itemCategory)) {
            response.steps.append("Warning: Item '" & itemName & "' has unknown category - skipping");
            continue;
        }

        categoryID = categoryMap[itemCategory];

        // Track sort order within category
        if (!structKeyExists(categoryItemOrder, itemCategory)) {
            categoryItemOrder[itemCategory] = 1;
        }
        itemOrder = categoryItemOrder[itemCategory];
        categoryItemOrder[itemCategory]++;

        // Check if item exists
        qItem = queryTimed("
            SELECT ID FROM Items
            WHERE BusinessID = :bizID
              AND Name = :name
              AND CategoryID = :catID
        ", { bizID: businessId, name: itemName, catID: categoryID }, { datasource: "payfrit" });

        if (qItem.recordCount > 0) {
            menuItemID = qItem.ID;
            // Update existing item
            queryTimed("
                UPDATE Items SET
                    Description = :desc,
                    Price = :price,
                    SortOrder = :sortOrder
                WHERE ID = :id
            ", {
                desc: itemDesc,
                price: itemPrice,
                sortOrder: itemOrder,
                id: menuItemID
            }, { datasource: "payfrit" });
        } else {
            queryTimed("
                INSERT INTO Items (
                    BusinessID, Name, Description, ParentItemID,
                    CategoryID, Price, IsActive, SortOrder
                ) VALUES (
                    :bizID, :name, :desc, 0, :catID, :price, 1, :sortOrder
                )
            ", {
                bizID: businessId,
                name: itemName,
                desc: itemDesc,
                catID: categoryID,
                price: itemPrice,
                sortOrder: itemOrder
            }, { datasource: "payfrit" });

            qNewItem = queryTimed("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
            menuItemID = qNewItem.id;
        }

        totalItems++;

        // Link modifier templates to this item
        modOrder = 1;
        for (m = 1; m <= arrayLen(itemModifiers); m++) {
            modRef = itemModifiers[m];
            // Handle both string modifier names and struct references
            if (isSimpleValue(modRef)) {
                modName = modRef;
            } else if (isStruct(modRef) && structKeyExists(modRef, "name")) {
                modName = modRef.name;
            } else {
                continue; // Skip invalid modifier reference
            }
            if (structKeyExists(templateMap, modName)) {
                templateItemID = templateMap[modName];

                // Check if link exists
                qLink = queryTimed("
                    SELECT 1 FROM lt_ItemID_TemplateItemID
                    WHERE ItemID = :itemID AND TemplateItemID = :templateID
                ", { itemID: menuItemID, templateID: templateItemID }, { datasource: "payfrit" });

                if (qLink.recordCount == 0) {
                    queryTimed("
                        INSERT INTO lt_ItemID_TemplateItemID (ItemID, TemplateItemID, SortOrder)
                        VALUES (:itemID, :templateID, :sortOrder)
                    ", {
                        itemID: menuItemID,
                        templateID: templateItemID,
                        sortOrder: modOrder
                    }, { datasource: "payfrit" });
                    totalLinks++;
                }
                modOrder++;
            }
        }
    }

    response.steps.append("Created/updated " & totalItems & " items with " & totalLinks & " modifier links");

    response.OK = true;
    response.summary = {
        "businessId": businessId,
        "categoriesProcessed": arrayLen(categories),
        "templatesProcessed": arrayLen(modTemplates),
        "itemsProcessed": totalItems,
        "linksCreated": totalLinks
    };

} catch (any e) {
    response.errors.append(e.message);
    if (len(e.detail)) {
        response.errors.append(e.detail);
    }
}

writeOutput(serializeJSON(response));
</cfscript>
