<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="buildLineItemsGraph" access="public" returntype="struct" output="false">
  <cfargument name="OrderID" type="numeric" required="true">

  <cfset var out = {}>
  <cfset out.items = {}> <!--- lineItemId -> struct --->
  <cfset out.children = {}> <!--- parentLineItemId -> array(lineItemId) --->
  <cfset out.itemMeta = {}> <!--- ItemID -> struct(meta) --->

  <cfset var qLI = queryExecute(
    "
      SELECT
        OrderLineItemID,
        OrderLineItemParentOrderLineItemID,
        OrderLineItemItemID,
        OrderLineItemIsDeleted
      FROM OrderLineItems
      WHERE OrderLineItemOrderID = ?
      ORDER BY OrderLineItemID
    ",
    [ { value = arguments.OrderID, cfsqltype = "cf_sql_integer" } ],
    { datasource = "payfrit" }
  )>

  <cfif qLI.recordCount EQ 0>
    <cfreturn out>
  </cfif>

  <cfset var itemIds = []>
  <cfloop query="qLI">
    <cfset out.items[qLI.OrderLineItemID] = {
      "id": qLI.OrderLineItemID,
      "parentId": qLI.OrderLineItemParentOrderLineItemID,
      "itemId": qLI.OrderLineItemItemID,
      "isDeleted": (qLI.OrderLineItemIsDeleted EQ true)
    }>
    <cfif NOT structKeyExists(out.children, qLI.OrderLineItemParentOrderLineItemID)>
      <cfset out.children[qLI.OrderLineItemParentOrderLineItemID] = []>
    </cfif>
    <cfset arrayAppend(out.children[qLI.OrderLineItemParentOrderLineItemID], qLI.OrderLineItemID)>
    <cfset arrayAppend(itemIds, qLI.OrderLineItemItemID)>
  </cfloop>

  <!--- Load meta for involved items --->
  <cfset var uniq = {} >
  <cfloop array="#itemIds#" index="iid">
    <cfset uniq[iid] = true>
  </cfloop>
  <cfset var uniqIds = structKeyArray(uniq)>
  <cfif arrayLen(uniqIds) GT 0>
    <cfset var inList = arrayToList(uniqIds)>
    <cfset var qMeta = queryExecute(
      "
        SELECT
          ItemID,
          ItemRequiresChildSelection,
          ItemMaxNumSelectionReq
        FROM Items
        WHERE ItemID IN (#inList#)
      ",
      [],
      { datasource = "payfrit" }
    )>

    <cfloop query="qMeta">
      <cfset out.itemMeta[qMeta.ItemID] = {
        "requires": qMeta.ItemRequiresChildSelection,
        "maxSel": qMeta.ItemMaxNumSelectionReq
      }>
    </cfloop>
  </cfif>

  <cfreturn out>
</cffunction>

<cffunction name="hasSelectedDescendant" access="public" returntype="boolean" output="false">
  <cfargument name="graph" type="struct" required="true">
  <cfargument name="lineItemId" type="numeric" required="true">

  <cfset var stack = []>
  <cfif structKeyExists(arguments.graph.children, arguments.lineItemId)>
    <cfset stack = duplicate(arguments.graph.children[arguments.lineItemId])>
  </cfif>

  <cfloop condition="arrayLen(stack) GT 0">
    <cfset var id = stack[arrayLen(stack)]>
    <cfset arrayDeleteAt(stack, arrayLen(stack))>

    <cfif structKeyExists(arguments.graph.items, id)>
      <cfset var node = arguments.graph.items[id]>
      <cfif NOT node.isDeleted>
        <cfreturn true>
      </cfif>
      <cfif structKeyExists(arguments.graph.children, id)>
        <cfset var kids = arguments.graph.children[id]>
        <cfloop array="#kids#" index="kidId">
          <cfset arrayAppend(stack, kidId)>
        </cfloop>
      </cfif>
    </cfif>
  </cfloop>

  <cfreturn false>
</cffunction>

<cfset data = readJsonBody()>
<cfset OrderID = val( structKeyExists(data,"OrderID") ? data.OrderID : 0 )>

<cfif OrderID LTE 0>
  <cfset apiAbort({ "OK": false, "ERROR": "missing_orderid", "MESSAGE": "OrderID is required.", "DETAIL": "" })>
</cfif>

<cftry>
  <cfset qOrder = queryExecute(
    "
      SELECT OrderID, OrderStatusID, OrderTypeID
      FROM Orders
      WHERE OrderID = ?
      LIMIT 1
    ",
    [ { value = OrderID, cfsqltype = "cf_sql_integer" } ],
    { datasource = "payfrit" }
  )>

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

  <cfif qOrder.OrderStatusID NEQ 0>
    <cfset apiAbort({ "OK": false, "ERROR": "bad_state", "MESSAGE": "Order is not in cart state.", "DETAIL": "" })>
  </cfif>

  <cfif qOrder.OrderTypeID NEQ 1>
    <cfset apiAbort({ "OK": false, "ERROR": "bad_type", "MESSAGE": "Only dine-in orders (OrderTypeID=1) are supported in this MVP submit endpoint.", "DETAIL": "" })>
  </cfif>

  <!--- Must have at least one non-deleted root line item --->
  <cfset qRoots = queryExecute(
    "
      SELECT COUNT(*) AS Cnt
      FROM OrderLineItems
      WHERE OrderLineItemOrderID = ?
        AND OrderLineItemParentOrderLineItemID = 0
        AND OrderLineItemIsDeleted = b'0'
    ",
    [ { value = OrderID, cfsqltype = "cf_sql_integer" } ],
    { datasource = "payfrit" }
  )>

  <cfif qRoots.Cnt LTE 0>
    <cfset apiAbort({ "OK": false, "ERROR": "empty_order", "MESSAGE": "Order has no items.", "DETAIL": "" })>
  </cfif>

  <!--- Validate requires-descendant and max immediate selections --->
  <cfset graph = buildLineItemsGraph(OrderID)>

  <!--- Loop all non-deleted nodes and validate --->
  <cfloop collection="#graph.items#" item="k">
    <cfset node = graph.items[k]>
    <cfif node.isDeleted>
      <!--- skip deleted nodes --->
      <cfcontinue>
    </cfif>

    <cfset meta = structKeyExists(graph.itemMeta, node.itemId) ? graph.itemMeta[node.itemId] : { "requires": 0, "maxSel": 0 }>

    <!--- max immediate selections --->
    <cfset maxSel = val(meta.maxSel)>
    <cfif maxSel GT 0>
      <cfset selCount = 0>
      <cfif structKeyExists(graph.children, node.id)>
        <cfset kids = graph.children[node.id]>
        <cfloop array="#kids#" index="kidId">
          <cfif structKeyExists(graph.items, kidId)>
            <cfset kidNode = graph.items[kidId]>
            <cfif NOT kidNode.isDeleted>
              <cfset selCount = selCount + 1>
            </cfif>
          </cfif>
        </cfloop>
      </cfif>

      <cfif selCount GT maxSel>
        <cfset apiAbort({
          "OK": false,
          "ERROR": "max_selection_exceeded",
          "MESSAGE": "Too many selections under a modifier group.",
          "DETAIL": "LineItemID #node.id# has #selCount# immediate children selected; max is #maxSel#."
        })>
      </cfif>
    </cfif>

    <!--- requires descendant selection --->
    <cfif val(meta.requires) EQ 1>
      <cfif NOT hasSelectedDescendant(graph, node.id)>
        <cfset apiAbort({
          "OK": false,
          "ERROR": "required_selection_missing",
          "MESSAGE": "A required modifier selection is missing.",
          "DETAIL": "LineItemID #node.id# requires at least one descendant selection."
        })>
      </cfif>
    </cfif>
  </cfloop>

  <!--- Submit: mark submitted + status 1 --->
  <cfset queryExecute(
    "
      UPDATE Orders
      SET
        OrderStatusID = 1,
        OrderSubmittedOn = ?,
        OrderLastEditedOn = ?
      WHERE OrderID = ?
    ",
    [
      { value = now(), cfsqltype = "cf_sql_timestamp" },
      { value = now(), cfsqltype = "cf_sql_timestamp" },
      { value = OrderID, cfsqltype = "cf_sql_integer" }
    ],
    { datasource = "payfrit" }
  )>

  <cfset apiAbort({ "OK": true, "ERROR": "", "OrderID": OrderID, "MESSAGE": "submitted" })>

  <cfcatch>
    <cfset apiAbort({
      "OK": false,
      "ERROR": "server_error",
      "MESSAGE": "DB error submitting order",
      "DETAIL": cfcatch.message
    })>
  </cfcatch>
</cftry>
