<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">

<cffunction name="readJsonBody" access="public" returntype="struct" output="false">
  <cfset var raw = getHttpRequestData().content>
  <cfif isNull(raw) OR len(trim(raw)) EQ 0>
    <cfreturn {}>
  </cfif>
  <cftry>
    <cfset var data = deserializeJSON(raw)>
    <cfif isStruct(data)>
      <cfreturn data>
    <cfelse>
      <cfreturn {}>
    </cfif>
    <cfcatch>
      <cfreturn {}>
    </cfcatch>
  </cftry>
</cffunction>

<cffunction name="apiAbort" access="public" returntype="void" output="true">
  <cfargument name="payload" type="struct" required="true">
  <cfcontent type="application/json; charset=utf-8">
  <cfoutput>#serializeJSON(arguments.payload)#</cfoutput>
  <cfabort>
</cffunction>

<cffunction name="nextId" access="public" returntype="numeric" output="false">
  <cfargument name="tableName" type="string" required="true">
  <cfargument name="idField" type="string" required="true">

  <cfset var q = queryTimed(
    "SELECT IFNULL(MAX(#arguments.idField#),0) + 1 AS NextID FROM #arguments.tableName#",
    [],
    { datasource = "payfrit" }
  )>
  <cfreturn q.NextID>
</cffunction>

<cffunction name="attachDefaultChildren" access="public" returntype="void" output="false">
  <cfargument name="OrderID" type="numeric" required="true">
  <cfargument name="ParentLineItemID" type="numeric" required="true">
  <cfargument name="ParentItemID" type="numeric" required="true">

  <!--- Find immediate children where checked by default --->
  <cfset var qKids = queryTimed(
    "
      SELECT ID, Price
      FROM Items
      WHERE ParentItemID = ?
        AND IsCheckedByDefault = 1
        AND IsActive = 1
      ORDER BY SortOrder, ID
    ",
    [ { value = arguments.ParentItemID, cfsqltype = "cf_sql_integer" } ],
    { datasource = "payfrit" }
  )>

  <!--- Also find default children from templates linked to this item --->
  <cfset var qTemplateKids = queryTimed(
    "
      SELECT i.ID, i.Price
      FROM lt_ItemID_TemplateItemID tl
      INNER JOIN Items i ON i.ParentItemID = tl.TemplateItemID
      WHERE tl.ItemID = ?
        AND i.IsCheckedByDefault = 1
        AND i.IsActive = 1
      ORDER BY i.SortOrder, i.ID
    ",
    [ { value = arguments.ParentItemID, cfsqltype = "cf_sql_integer" } ],
    { datasource = "payfrit" }
  )>

  <!--- Store debug info in request scope for response --->
  <cfif NOT structKeyExists(request, "attachDebug")>
    <cfset request.attachDebug = []>
  </cfif>
  <cfset arrayAppend(request.attachDebug, "attachDefaultChildren: OrderID=#arguments.OrderID#, ParentLI=#arguments.ParentLineItemID#, ParentItemID=#arguments.ParentItemID#")>
  <cfset arrayAppend(request.attachDebug, "  qKids=#qKids.recordCount# rows, qTemplateKids=#qTemplateKids.recordCount# rows")>

  <!--- Process direct children --->
  <cfloop query="qKids">
    <cfset arrayAppend(request.attachDebug, "  -> direct child: ItemID=#qKids.ID#")>
    <cfset processDefaultChild(arguments.OrderID, arguments.ParentLineItemID, qKids.ID, qKids.Price)>
  </cfloop>

  <!--- Process template children --->
  <cfloop query="qTemplateKids">
    <cfset arrayAppend(request.attachDebug, "  -> template child: ItemID=#qTemplateKids.ID#")>
    <cfset processDefaultChild(arguments.OrderID, arguments.ParentLineItemID, qTemplateKids.ID, qTemplateKids.Price)>
  </cfloop>
</cffunction>

<cffunction name="processDefaultChild" access="public" returntype="void" output="false">
  <cfargument name="OrderID" type="numeric" required="true">
  <cfargument name="ParentLineItemID" type="numeric" required="true">
  <cfargument name="ItemID" type="numeric" required="true">
  <cfargument name="Price" type="numeric" required="true">

  <!--- If existing, undelete; else insert new --->
  <cfset var qExisting = queryTimed(
    "
      SELECT ID
      FROM OrderLineItems
      WHERE OrderID = ?
        AND ParentOrderLineItemID = ?
        AND ItemID = ?
      LIMIT 1
    ",
    [
      { value = arguments.OrderID, cfsqltype = "cf_sql_integer" },
      { value = arguments.ParentLineItemID, cfsqltype = "cf_sql_integer" },
      { value = arguments.ItemID, cfsqltype = "cf_sql_integer" }
    ],
    { datasource = "payfrit" }
  )>

  <cfif qExisting.recordCount GT 0>
    <cfset queryTimed(
      "
        UPDATE OrderLineItems
        SET IsDeleted = b'0'
        WHERE ID = ?
      ",
      [ { value = qExisting.ID, cfsqltype = "cf_sql_integer" } ],
      { datasource = "payfrit" }
    )>
    <cfset attachDefaultChildren(arguments.OrderID, qExisting.ID, arguments.ItemID)>
  <cfelse>
    <cfset var NewLIID = nextId("OrderLineItems","ID")>
    <cfset queryTimed(
      "
        INSERT INTO OrderLineItems (
          ID,
          ParentOrderLineItemID,
          OrderID,
          ItemID,
          StatusID,
          Price,
          Quantity,
          Remark,
          IsDeleted,
          AddedOn
        ) VALUES (
          ?,
          ?,
          ?,
          ?,
          0,
          ?,
          1,
          NULL,
          b'0',
          ?
        )
      ",
      [
        { value = NewLIID, cfsqltype = "cf_sql_integer" },
        { value = arguments.ParentLineItemID, cfsqltype = "cf_sql_integer" },
        { value = arguments.OrderID, cfsqltype = "cf_sql_integer" },
        { value = arguments.ItemID, cfsqltype = "cf_sql_integer" },
        { value = arguments.Price, cfsqltype = "cf_sql_decimal" },
        { value = now(), cfsqltype = "cf_sql_timestamp" }
      ],
      { datasource = "payfrit" }
    )>
    <cfset attachDefaultChildren(arguments.OrderID, NewLIID, arguments.ItemID)>
  </cfif>
</cffunction>

<cffunction name="loadCartPayload" access="public" returntype="struct" output="false">
  <cfargument name="OrderID" type="numeric" required="true">

  <cfset var out = {}>
  <cfset var qOrder = queryTimed(
    "
      SELECT
        o.ID,
        o.UUID,
        o.UserID,
        o.BusinessID,
        o.DeliveryMultiplier,
        o.OrderTypeID,
        o.DeliveryFee,
        o.StatusID,
        o.AddressID,
        o.PaymentID,
        o.Remarks,
        o.AddedOn,
        o.LastEditedOn,
        o.SubmittedOn,
        o.ServicePointID,
        COALESCE(b.DeliveryFlatFee, 0) AS BusinessDeliveryFee
      FROM Orders o
      LEFT JOIN Businesses b ON b.ID = o.BusinessID
      WHERE o.ID = ?
      LIMIT 1
    ",
    [ { value = arguments.OrderID, cfsqltype = "cf_sql_integer" } ],
    { datasource = "payfrit" }
  )>

  <cfif qOrder.recordCount EQ 0>
    <cfreturn { "OK": false, "ERROR": "not_found", "MESSAGE": "Order not found", "DETAIL": "" }>
  </cfif>

  <cfset out.ORDER = {
    "OrderID": val(qOrder.ID),
    "UUID": qOrder.UUID ?: "",
    "UserID": val(qOrder.UserID),
    "BusinessID": val(qOrder.BusinessID),
    "DeliveryMultiplier": val(qOrder.DeliveryMultiplier),
    "OrderTypeID": val(qOrder.OrderTypeID),
    "DeliveryFee": val(qOrder.DeliveryFee),
    "StatusID": val(qOrder.StatusID),
    "AddressID": val(qOrder.AddressID),
    "PaymentID": val(qOrder.PaymentID),
    "Remarks": qOrder.Remarks ?: "",
    "AddedOn": qOrder.AddedOn,
    "LastEditedOn": qOrder.LastEditedOn,
    "SubmittedOn": qOrder.SubmittedOn,
    "ServicePointID": val(qOrder.ServicePointID),
    "BusinessDeliveryFee": val(qOrder.BusinessDeliveryFee)
  }>

  <cfset var qLI = queryTimed(
    "
      SELECT
        oli.ID,
        oli.ParentOrderLineItemID,
        oli.OrderID,
        oli.ItemID,
        oli.StatusID,
        oli.Price,
        oli.Quantity,
        oli.Remark,
        oli.IsDeleted,
        oli.AddedOn,
        i.Name,
        i.ParentItemID,
        i.IsCheckedByDefault,
        parent.Name AS ItemParentName
      FROM OrderLineItems oli
      INNER JOIN Items i ON i.ID = oli.ItemID
      LEFT JOIN Items parent ON parent.ID = i.ParentItemID
      WHERE oli.OrderID = ?
      ORDER BY oli.ID
    ",
    [ { value = arguments.OrderID, cfsqltype = "cf_sql_integer" } ],
    { datasource = "payfrit" }
  )>

  <cfset var rows = []>
  <cfloop query="qLI">
    <cfset arrayAppend(rows, {
      "OrderLineItemID": val(qLI.ID),
      "ParentOrderLineItemID": val(qLI.ParentOrderLineItemID),
      "OrderID": val(qLI.OrderID),
      "ItemID": val(qLI.ItemID),
      "StatusID": val(qLI.StatusID),
      "Price": val(qLI.Price),
      "Quantity": val(qLI.Quantity),
      "Remark": qLI.Remark ?: "",
      "IsDeleted": val(qLI.IsDeleted),
      "AddedOn": qLI.AddedOn,
      "Name": qLI.Name ?: "",
      "ParentItemID": val(qLI.ParentItemID),
      "ItemParentName": qLI.ItemParentName ?: "",
      "IsCheckedByDefault": val(qLI.IsCheckedByDefault)
    })>
  </cfloop>

  <cfset out.ORDERLINEITEMS = rows>
  <cfset out.OK = true>
  <cfset out.ERROR = "">
  <cfreturn out>
</cffunction>

<cfset data = readJsonBody()>

<cfset OrderID = val( structKeyExists(data,"OrderID") ? data.OrderID : 0 )>
<cfset ParentLineItemID = val( structKeyExists(data,"ParentOrderLineItemID") ? data.ParentOrderLineItemID : 0 )>
<cfset OriginalItemID = structKeyExists(data,"ItemID") ? data.ItemID : 0>
<cfset ItemID = val(OriginalItemID)>

<!--- Decode virtual IDs from menu API (format: menuItemID * 100000 + realItemID) --->
<!--- If ItemID > 100000, extract the real ItemID --->
<cfset WasDecoded = false>
<cfif ItemID GT 100000>
  <cfset WasDecoded = true>
  <cfset ItemID = ItemID MOD 100000>
</cfif>

<!--- Store debug info for response --->
<cfset request.itemDebug = {
  "OriginalItemID": OriginalItemID,
  "ParsedItemID": val(OriginalItemID),
  "WasDecoded": WasDecoded,
  "FinalItemID": ItemID
}>
<cfset IsSelected = false>
<cfif structKeyExists(data, "IsSelected")>
  <cfset IsSelected = (data.IsSelected EQ true OR data.IsSelected EQ 1 OR (isSimpleValue(data.IsSelected) AND lcase(toString(data.IsSelected)) EQ "true"))>
</cfif>
<cfset Quantity = structKeyExists(data,"Quantity") ? val(data.Quantity) : 0>
<cfset Remark = structKeyExists(data,"Remark") ? toString(data.Remark) : "">
<cfset ForceNew = false>
<cfif structKeyExists(data, "ForceNew")>
  <cfset ForceNew = (data.ForceNew EQ true OR data.ForceNew EQ 1 OR (isSimpleValue(data.ForceNew) AND lcase(toString(data.ForceNew)) EQ "true"))>
</cfif>

<cfif OrderID LTE 0 OR ItemID LTE 0>
  <cfset apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "OrderID and ItemID are required.", "DETAIL": "" })>
</cfif>

<cftry>
  <!--- Load item price --->
  <cfset qItem = queryTimed(
    "
      SELECT ID, Price, ParentItemID, IsActive
      FROM Items
      WHERE ID = ?
      LIMIT 1
    ",
    [ { value = ItemID, cfsqltype = "cf_sql_integer" } ],
    { datasource = "payfrit" }
  )>

  <cfif qItem.recordCount EQ 0 OR qItem.IsActive NEQ 1>
    <cfset apiAbort({
      "OK": false,
      "ERROR": "bad_item",
      "MESSAGE": "Item not found or inactive. Original=#OriginalItemID# Decoded=#ItemID# WasDecoded=#WasDecoded#",
      "DETAIL": "",
      "DEBUG_ITEM": request.itemDebug
    })>
  </cfif>

  <!--- Root vs modifier rules --->
  <cfif ParentLineItemID EQ 0>
    <!--- Root item quantity required when selecting --->
    <cfif IsSelected AND Quantity LTE 0>
      <cfset apiAbort({ "OK": false, "ERROR": "bad_quantity", "MESSAGE": "Root line items require Quantity > 0.", "DETAIL": "" })>
    </cfif>
  <cfelse>
    <!--- Modifier quantity is implicitly tied => force 1 when selecting --->
    <cfif IsSelected>
      <cfset Quantity = 1>
    <cfelse>
      <cfset Quantity = 1>
    </cfif>

    <!--- Check if this is an exclusive selection group (max = 1) --->
    <!--- If so, deselect siblings when selecting a new item --->
    <cfif IsSelected>
      <!--- Get the parent line item's ItemID to check maxSel --->
      <cfset qParentLI = queryTimed(
        "SELECT ItemID FROM OrderLineItems WHERE ID = ? LIMIT 1",
        [ { value = ParentLineItemID, cfsqltype = "cf_sql_integer" } ],
        { datasource = "payfrit" }
      )>

      <cfif qParentLI.recordCount GT 0>
        <!--- Check if this parent item has maxSel = 1 (exclusive selection) --->
        <cfset qParentItem = queryTimed(
          "SELECT MaxNumSelectionReq FROM Items WHERE ID = ? LIMIT 1",
          [ { value = qParentLI.ItemID, cfsqltype = "cf_sql_integer" } ],
          { datasource = "payfrit" }
        )>

        <cfif qParentItem.recordCount GT 0 AND val(qParentItem.MaxNumSelectionReq) EQ 1>
          <!--- Exclusive selection: deselect all other siblings under this parent --->
          <cfset queryTimed(
            "
              UPDATE OrderLineItems
              SET IsDeleted = b'1'
              WHERE OrderID = ?
                AND ParentOrderLineItemID = ?
                AND ItemID != ?
                AND IsDeleted = b'0'
            ",
            [
              { value = OrderID, cfsqltype = "cf_sql_integer" },
              { value = ParentLineItemID, cfsqltype = "cf_sql_integer" },
              { value = ItemID, cfsqltype = "cf_sql_integer" }
            ],
            { datasource = "payfrit" }
          )>
        </cfif>
      </cfif>
    </cfif>
  </cfif>

  <!--- Find existing line item (by order, parent LI, item) - unless ForceNew is set --->
  <cfif ForceNew>
    <!--- ForceNew: Skip existing lookup, will always create new line item --->
    <cfset qExisting = queryNew("ID", "integer")>
  <cfelse>
    <cfset qExisting = queryTimed(
      "
        SELECT ID
        FROM OrderLineItems
        WHERE OrderID = ?
          AND ParentOrderLineItemID = ?
          AND ItemID = ?
        LIMIT 1
      ",
      [
        { value = OrderID, cfsqltype = "cf_sql_integer" },
        { value = ParentLineItemID, cfsqltype = "cf_sql_integer" },
        { value = ItemID, cfsqltype = "cf_sql_integer" }
      ],
      { datasource = "payfrit" }
    )>
  </cfif>

  <!--- Initialize debug array at start of processing --->
  <cfset request.attachDebug = ["Flow start: qExisting.recordCount=#qExisting.recordCount#, IsSelected=#IsSelected#"]>

  <cfif qExisting.recordCount GT 0>
    <!--- Update existing --->
    <cfset arrayAppend(request.attachDebug, "Path: update existing")>
    <cfif IsSelected>
      <cfset queryTimed(
        "
          UPDATE OrderLineItems
          SET
            IsDeleted = b'0',
            Quantity = ?,
            Price = ?,
            Remark = ?,
            StatusID = 0
          WHERE ID = ?
        ",
        [
          { value = Quantity, cfsqltype = "cf_sql_integer" },
          { value = qItem.Price, cfsqltype = "cf_sql_decimal" },
          { value = Remark, cfsqltype = "cf_sql_varchar", null = (len(trim(Remark)) EQ 0) },
          { value = qExisting.ID, cfsqltype = "cf_sql_integer" }
        ],
        { datasource = "payfrit" }
      )>

      <!--- Attach default children for this node (recursively) --->
      <cfif NOT structKeyExists(request, "attachDebug")>
        <cfset request.attachDebug = []>
      </cfif>
      <cfset arrayAppend(request.attachDebug, "BEFORE attachDefaultChildren call: OrderID=#OrderID#, LIID=#qExisting.ID#, ItemID=#ItemID#")>
      <cfset attachDefaultChildren(OrderID, qExisting.ID, ItemID)>
      <cfset arrayAppend(request.attachDebug, "AFTER attachDefaultChildren call")>
    <cfelse>
      <cfset queryTimed(
        "
          UPDATE OrderLineItems
          SET IsDeleted = b'1'
          WHERE ID = ?
        ",
        [ { value = qExisting.ID, cfsqltype = "cf_sql_integer" } ],
        { datasource = "payfrit" }
      )>
    </cfif>
  <cfelse>
    <!--- Insert new if selecting, otherwise no-op --->
    <cfif IsSelected>
      <cfset NewLIID = nextId("OrderLineItems","ID")>

      <cfset queryTimed(
        "
          INSERT INTO OrderLineItems (
            ID,
            ParentOrderLineItemID,
            OrderID,
            ItemID,
            StatusID,
            Price,
            Quantity,
            Remark,
            IsDeleted,
            AddedOn
          ) VALUES (
            ?,
            ?,
            ?,
            ?,
            0,
            ?,
            ?,
            ?,
            b'0',
            ?
          )
        ",
        [
          { value = NewLIID, cfsqltype = "cf_sql_integer" },
          { value = ParentLineItemID, cfsqltype = "cf_sql_integer" },
          { value = OrderID, cfsqltype = "cf_sql_integer" },
          { value = ItemID, cfsqltype = "cf_sql_integer" },
          { value = qItem.Price, cfsqltype = "cf_sql_decimal" },
          { value = (ParentLineItemID EQ 0 ? Quantity : 1), cfsqltype = "cf_sql_integer" },
          { value = Remark, cfsqltype = "cf_sql_varchar", null = (len(trim(Remark)) EQ 0) },
          { value = now(), cfsqltype = "cf_sql_timestamp" }
        ],
        { datasource = "payfrit" }
      )>

      <cfset attachDefaultChildren(OrderID, NewLIID, ItemID)>
    </cfif>
  </cfif>

  <!--- Touch order last edited --->
  <cftry>
    <cfset queryTimed(
      "UPDATE Orders SET LastEditedOn = ? WHERE ID = ?",
      [
        { value = now(), cfsqltype = "cf_sql_timestamp" },
        { value = OrderID, cfsqltype = "cf_sql_integer" }
      ],
      { datasource = "payfrit" }
    )>
    <cfcatch>
      <cfset apiAbort({
        "OK": false,
        "ERROR": "update_order_error",
        "MESSAGE": "Error updating order timestamp: " & cfcatch.message,
        "DETAIL": cfcatch.tagContext[1].template & ":" & cfcatch.tagContext[1].line
      })>
    </cfcatch>
  </cftry>

  <cftry>
    <cfset payload = loadCartPayload(OrderID)>
    <!--- Add debug info to response --->
    <cfif structKeyExists(request, "attachDebug")>
      <cfset payload["DEBUG_ATTACH"] = request.attachDebug>
    </cfif>
    <cfset apiAbort(payload)>
    <cfcatch>
      <cfset apiAbort({
        "OK": false,
        "ERROR": "load_cart_error",
        "MESSAGE": "Error loading cart: " & cfcatch.message & " | " & (structKeyExists(cfcatch, "detail") ? cfcatch.detail : ""),
        "DETAIL": cfcatch.tagContext[1].template & ":" & cfcatch.tagContext[1].line
      })>
    </cfcatch>
  </cftry>

  <cfcatch>
    <cfset apiAbort({
      "OK": false,
      "ERROR": "server_error",
      "MESSAGE": "DB error setting line item: " & cfcatch.message & " | " & (structKeyExists(cfcatch, "detail") ? cfcatch.detail : "") & " | " & (structKeyExists(cfcatch, "sql") ? cfcatch.sql : ""),
      "DETAIL": cfcatch.tagContext[1].template & ":" & cfcatch.tagContext[1].line
    })>
  </cfcatch>
</cftry>
