## Cypher-like Graph Query Extension for BaraQL
import std/tables
import std/strutils
import std/locks
import engine

type
  CypherNode* = object
    variable*: string
    label*: string
    properties*: Table[string, string]

  CypherEdge* = object
    variable*: string
    label*: string
    direction*: string  # "->", "<-", "--"
    properties*: Table[string, string]

  CypherPattern* = object
    nodes*: seq[CypherNode]
    edges*: seq[CypherEdge]

  CypherQuery* = object
    kind*: string  # "MATCH", "CREATE", "MERGE", "DELETE"
    pattern*: CypherPattern
    whereClause*: string
    returnExprs*: seq[string]
    orderBy*: string
    limit*: int

  CypherResult* = object
    columns*: seq[string]
    rows*: seq[seq[string]]

proc parseCypher*(query: string): CypherQuery =
  result = CypherQuery(returnExprs: @[], limit: 0)
  let upper = query.toUpper().strip()

  if upper.startsWith("MATCH"):
    result.kind = "MATCH"
  elif upper.startsWith("CREATE"):
    result.kind = "CREATE"
  elif upper.startsWith("MERGE"):
    result.kind = "MERGE"
  else:
    return

  # Parse node pattern: (variable:Label {props})
  var pos = 0
  var nodes: seq[CypherNode] = @[]
  var edges: seq[CypherEdge] = @[]

  while pos < query.len:
    if query[pos] == '(':
      # Parse node
      inc pos
      var variable = ""
      var label = ""
      var props = initTable[string, string]()

      # Read variable name
      while pos < query.len and query[pos] notin {':', ' ', '{', ')'}:
        variable &= query[pos]
        inc pos

      if pos < query.len and query[pos] == ':':
        inc pos
        while pos < query.len and query[pos] notin {' ', '{', ')'}:
          label &= query[pos]
          inc pos

      if pos < query.len and query[pos] == '{':
        inc pos
        var key = ""
        var value = ""
        var inKey = true
        while pos < query.len and query[pos] != '}':
          let ch = query[pos]
          if ch == ':':
            inKey = false
          elif ch in {',', ' '}:
            if key.len > 0 and value.len > 0:
              props[key.strip()] = value.strip().strip(chars = {'"'})
              key = ""
              value = ""
              inKey = true
          elif inKey:
            key &= ch
          else:
            value &= ch
          inc pos
        if key.len > 0 and value.len > 0:
          props[key.strip()] = value.strip().strip(chars = {'"'})
        inc pos  # skip }

      nodes.add(CypherNode(variable: variable, label: label, properties: props))
      inc pos  # skip )
    elif query[pos] == '-' or query[pos] == '<' or query[pos] == '[':
      # Parse edge
      var direction = ""
      var edgeVar = ""
      var edgeLabel = ""

      if query[pos] == '<':
        inc pos
        direction = "<-"

      if query[pos] == '-':
        inc pos
        if direction == "":
          direction = "-"
        else:
          direction &= "-"

      if pos < query.len and query[pos] == '[':
        inc pos
        while pos < query.len and query[pos] notin {']', ':'}:
          edgeVar &= query[pos]
          inc pos
        if pos < query.len and query[pos] == ':':
          inc pos
          while pos < query.len and query[pos] != ']':
            edgeLabel &= query[pos]
            inc pos
        inc pos  # skip ]

      if pos < query.len and query[pos] == '-':
        inc pos
        direction &= "-"
        if pos < query.len and query[pos] == '>':
          inc pos
          direction &= ">"

      edges.add(CypherEdge(variable: edgeVar, label: edgeLabel, direction: direction))
    else:
      inc pos

  result.pattern.nodes = nodes
  result.pattern.edges = edges

  # Parse WHERE
  let wherePos = query.toUpper().find(" WHERE ")
  if wherePos >= 0:
    let whereStart = wherePos + 7
    let returnPos = query.toUpper().find(" RETURN ")
    if returnPos > wherePos:
      result.whereClause = query[whereStart..<returnPos].strip()
    else:
      result.whereClause = query[whereStart..^1].strip()

  # Parse RETURN
  let returnPos = query.toUpper().find(" RETURN ")
  if returnPos >= 0:
    let returnContent = query[returnPos + 8..^1]
    for expr in returnContent.split(","):
      let trimmed = expr.strip()
      if trimmed.len > 0:
        result.returnExprs.add(trimmed)

  # Parse ORDER BY
  let orderPos = query.toUpper().find(" ORDER BY ")
  if orderPos >= 0:
    let limPos = query.toUpper().find(" LIMIT ")
    if limPos > orderPos:
      result.orderBy = query[orderPos + 9..<limPos].strip()
    else:
      result.orderBy = query[orderPos + 9..^1].strip()

  # Parse LIMIT
  let limPos = query.toUpper().find(" LIMIT ")
  if limPos >= 0:
    try:
      result.limit = parseInt(query[limPos + 7..^1].strip())
    except CatchableError:
      result.limit = 0

proc executeCypher*(g: Graph, query: CypherQuery): CypherResult =
  result = CypherResult(columns: @[], rows: @[])

  if query.pattern.nodes.len == 0:
    return

  acquire(g.lock)
  defer: release(g.lock)

  # Basic MATCH execution
  if query.kind == "MATCH":
    # For each node matching the pattern, collect results
    for nodeId, node in g.nodes:
      let patternNode = query.pattern.nodes[0]
      if patternNode.label.len == 0 or node.label == patternNode.label:
        var propsMatch = true
        for pk, pv in patternNode.properties:
          if node.properties.getOrDefault(pk, "") != pv:
            propsMatch = false
            break
        if propsMatch:
          var row: seq[string] = @[]
          for expr in query.returnExprs:
            if expr == patternNode.variable:
              row.add(node.label)
            elif expr.startsWith(patternNode.variable & "."):
              let propName = expr[expr.find('.') + 1 .. ^1]
              row.add(node.properties.getOrDefault(propName, ""))
            else:
              row.add(expr)
          result.rows.add(row)

    result.columns = query.returnExprs
    if query.limit > 0 and result.rows.len > query.limit:
      result.rows = result.rows[0..<query.limit]

proc toCypher*(query: string): string =
  ## Convert basic BaraQL to Cypher-like syntax for graph queries
  let upper = query.toUpper()
  if upper.startsWith("SELECT") and upper.contains("MATCH"):
    return query
  return query

# ---------------------------------------------------------------------------
# Cypher → BaraQL GRAPH_TABLE translation
# ---------------------------------------------------------------------------

proc cypherToSql*(cypher: string): string =
  ## Translate a Cypher MATCH query to BaraQL GRAPH_TABLE syntax.
  ## Example:
  ##   MATCH (a:Person)-[r:KNOWS]->(b:Person) WHERE a.name = 'Alice' RETURN b.name
  ##   → SELECT b.name FROM GRAPH_TABLE(g MATCH (a)-[r]->(b) COLUMNS (a.label, b.name))
  let trimmed = cypher.strip()
  if not trimmed.toUpper().startsWith("MATCH"):
    return cypher  # Not a Cypher MATCH query, return as-is

  # Extract parts: MATCH pattern [WHERE ...] [RETURN ...] [ORDER BY ...] [LIMIT n]
  var pattern = ""
  var whereClause = ""
  var returnClause = ""
  var orderByClause = ""
  var limitVal = ""

  var remaining = trimmed[5..^1]  # Strip "MATCH"

  # Extract the MATCH pattern up to WHERE/RETURN/ORDER/LIMIT
  let upperRemaining = remaining.toUpper()
  let wherePos = upperRemaining.find("WHERE")
  let returnPos = upperRemaining.find("RETURN")
  let orderPos = upperRemaining.find("ORDER")
  let limitPos = upperRemaining.find("LIMIT")

  var patternEnd = remaining.len
  for pos in [wherePos, returnPos, orderPos, limitPos]:
    if pos > 0 and pos < patternEnd:
      patternEnd = pos

  pattern = remaining[0..<patternEnd].strip()

  if returnPos > 0:
    var returnStart = returnPos + 6
    var returnEnd = remaining.len
    for pos in [orderPos, limitPos]:
      if pos > returnStart and pos < returnEnd:
        returnEnd = pos
    returnClause = remaining[returnStart..<returnEnd].strip()

  if wherePos > 0 and (wherePos < returnPos or returnPos < 0):
    var whereStart = wherePos + 5
    var whereEnd = remaining.len
    for pos in [returnPos, orderPos, limitPos]:
      if pos > whereStart and pos < whereEnd:
        whereEnd = pos
    whereClause = remaining[whereStart..<whereEnd].strip()

  if orderPos > 0:
    var orderStart = orderPos + 5
    if upperRemaining[orderPos + 5..<orderPos + 7] == "BY":
      orderStart = orderPos + 8
    var orderEnd = remaining.len
    if limitPos > orderStart and limitPos < orderEnd:
      orderEnd = limitPos
    orderByClause = remaining[orderStart..<orderEnd].strip()

  if limitPos > 0:
    var limitStart = limitPos + 5
    limitVal = remaining[limitStart..^1].strip().split(' ')[0]

  # Parse pattern: (a:Label)-[r:TYPE]->(b:Label)
  # Extract graph name from context or use "g"
  var graphName = "g"
  var patternNodes: seq[(string, string)] = @[]  # (variable, label)
  var patternEdges: seq[(string, string, string)] = @[]  # (variable, label, direction)

  var i = 0
  while i < pattern.len:
    if pattern[i] == '(':
      inc i
      var nodeVar = ""
      var nodeLabel = ""
      while i < pattern.len and pattern[i] != ')' and pattern[i] != ':':
        if pattern[i] != ' ':
          nodeVar.add(pattern[i])
        inc i
      if i < pattern.len and pattern[i] == ':':
        inc i
        while i < pattern.len and pattern[i] != ')' and pattern[i] != ' ':
          nodeLabel.add(pattern[i])
          inc i
      if i < pattern.len and pattern[i] == ')':
        inc i
      patternNodes.add((nodeVar, nodeLabel))
    elif pattern[i] == '[':
      inc i
      var edgeVar = ""
      var edgeLabel = ""
      var edgeDir = "->"
      while i < pattern.len and pattern[i] != ']' and pattern[i] != ':':
        if pattern[i] != ' ':
          edgeVar.add(pattern[i])
        inc i
      if i < pattern.len and pattern[i] == ':':
        inc i
        while i < pattern.len and pattern[i] != ']' and pattern[i] != ' ':
          edgeLabel.add(pattern[i])
          inc i
      if i < pattern.len and pattern[i] == ']':
        inc i
      if i < pattern.len and (pattern[i] == '-' or pattern[i] == '<'):
        edgeDir = ""
        while i < pattern.len and pattern[i] != '(':
          if pattern[i] notin {' ', '-'}:
            edgeDir.add(pattern[i])
          inc i
      patternEdges.add((edgeVar, edgeLabel, edgeDir))
    elif pattern[i] in {'-', '<', '>'}:
      inc i
    else:
      inc i

  # Build GRAPH_TABLE SQL
  var columns: seq[string] = @[]
  if returnClause.len > 0:
    for part in returnClause.split(','):
      let col = part.strip()
      if col == "*": continue
      columns.add(col)

  # Build pattern string for GRAPH_TABLE
  var graphPattern = ""
  for j in 0 ..< patternNodes.len:
    graphPattern.add("(" & patternNodes[j][0] & ")")
    if j < patternEdges.len:
      graphPattern.add("-[" & patternEdges[j][0] & "]->")
    elif j < patternNodes.len - 1:
      graphPattern.add("-")

  # Build SQL
  let colsStr = if columns.len > 0: columns.join(", ") else: "*"

  var sql = "SELECT " & colsStr & " FROM GRAPH_TABLE(" & graphName & " MATCH " & graphPattern

  sql.add(" COLUMNS (")
  if columns.len > 0:
    sql.add(columns.join(", "))
  sql.add("))")

  if whereClause.len > 0:
    sql.add(" WHERE ")
    sql.add(whereClause)
  if orderByClause.len > 0:
    sql.add(" ORDER BY ")
    sql.add(orderByClause)
  if limitVal.len > 0:
    sql.add(" LIMIT ")
    sql.add(limitVal)

  return sql

proc matchNodes*(g: Graph, label: string,
                 props: Table[string, string] = initTable[string, string]()): seq[GraphNode] =
  result = @[]
  for nodeId, node in g.nodes:
    if label.len == 0 or node.label == label:
      var match = true
      for pk, pv in props:
        if node.properties.getOrDefault(pk, "") != pv:
          match = false
          break
      if match:
        result.add(node)
