# SPDX-License-Identifier: LUL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus SDK.
# See legal/LICENSE_UNBOUND.md for license terms.

# MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
# NipBox KDL Core (The Semantic Spine)
# Defines the typed object system for the Sovereign Shell.

import strutils
import std/assertions

type
  ValueKind* = enum
    VString, VInt, VBool, VNull

  Value* = object
    case kind*: ValueKind
    of VString: s*: string
    of VInt: i*: int
    of VBool: b*: bool
    of VNull: discard

  # A KDL Node: name arg1 arg2 key=val { children }
  Node* = ref object
    name*: string
    args*: seq[Value]
    props*: seq[tuple[key: string, val: Value]]
    children*: seq[Node]

# --- Constructors ---

proc newVal*(s: string): Value = Value(kind: VString, s: s)
proc newVal*(i: int): Value = Value(kind: VInt, i: i)
proc newVal*(b: bool): Value = Value(kind: VBool, b: b)
proc newNull*(): Value = Value(kind: VNull)

proc newNode*(name: string): Node =
  new(result)
  result.name = name
  result.args = @[]
  result.props = @[]
  result.children = @[]

proc addArg*(n: Node, v: Value) =
  n.args.add(v)

proc addProp*(n: Node, key: string, v: Value) =
  n.props.add((key, v))

proc addChild*(n: Node, child: Node) =
  n.children.add(child)

# --- Serialization (The Renderer) ---

proc `$`*(v: Value): string =
  case v.kind
  of VString: "\"" & v.s & "\"" # TODO: Escape quotes properly
  of VInt: $v.i
  of VBool: $v.b
  of VNull: "null"

proc render*(n: Node, indent: int = 0): string =
  let prefix = repeat(' ', indent)
  var line = prefix & n.name

  # Args
  for arg in n.args:
    line.add(" " & $arg)

  # Props
  for prop in n.props:
    line.add(" " & prop.key & "=" & $prop.val)

  # Children
  if n.children.len > 0:
    line.add(" {\n")
    for child in n.children:
      line.add(render(child, indent + 2))
    line.add(prefix & "}\n")
  else:
    line.add("\n")

  return line

# Table View (For Flat Lists)
proc renderTable*(nodes: seq[Node]): string =
  var s = ""
  for n in nodes:
    s.add(render(n))
  return s

# --- Parser ---

type Parser = ref object
  input: string
  pos: int

proc peek(p: Parser): char =
  if p.pos >= p.input.len: return '\0'
  return p.input[p.pos]

proc next(p: Parser): char =
  if p.pos >= p.input.len: return '\0'
  result = p.input[p.pos]
  p.pos.inc

proc skipSpace(p: Parser) =
  while true:
    let c = p.peek()
    if c == ' ' or c == '\t' or c == '\r': discard p.next()
    else: break

proc parseIdentifier(p: Parser): string =
  # Simple identifier: strictly alphanumeric + _ - for now
  # TODO: Quoted identifiers
  if p.peek() == '"':
    discard p.next()
    while true:
      let c = p.next()
      if c == '\0': break
      if c == '"': break
      result.add(c)
  else:
    while true:
      let c = p.peek()
      if c in {'a'..'z', 'A'..'Z', '0'..'9', '_', '-', '.', '/'}:
        result.add(p.next())
      else: break

proc parseValue(p: Parser): Value =
  skipSpace(p)
  let c = p.peek()
  if c == '"':
    # String
    discard p.next()
    var s = ""
    while true:
      let ch = p.next()
      if ch == '\0': break
      if ch == '"': break
      s.add(ch)
    return newVal(s)
  elif c in {'0'..'9', '-'}:
    # Number (Int only for now)
    var s = ""
    s.add(p.next())
    while p.peek() in {'0'..'9'}:
      s.add(p.next())
    try:
      return newVal(parseInt(s))
    except:
      return newVal(0)
  elif c == 't': # true
    if p.input.substr(p.pos, p.pos+3) == "true":
      p.pos += 4
      return newVal(true)
  elif c == 'f': # false
    if p.input.substr(p.pos, p.pos+4) == "false":
      p.pos += 5
      return newVal(false)
  elif c == 'n': # null
    if p.input.substr(p.pos, p.pos+3) == "null":
      p.pos += 4
      return newNull()

  # Fallback: Bare string identifier
  return newVal(parseIdentifier(p))

proc parseNode(p: Parser): Node =
  skipSpace(p)
  let name = parseIdentifier(p)
  if name.len == 0: return nil

  var node = newNode(name)

  while true:
    skipSpace(p)
    let c = p.peek()
    if c == '\n' or c == ';' or c == '}' or c == '\0': break
    if c == '{': break # Children start
    
    # Arg or Prop?
    # Peek ahead to see if next is identifier=value
    # Simple heuristic: parse identifier, if next char is '=', it's a prop.
    let startPos = p.pos
    let id = parseIdentifier(p)
    if id.len > 0 and p.peek() == '=':
      # Property
      discard p.next() # skip =
      let val = parseValue(p)
      node.addProp(id, val)
    else:
      # Argument
      # Backtrack? Or realize we parsed a value?
      # If `id` was a bare string value, it works.
      # If `id` was quoted string, `parseIdentifier` handled it.
      # But `parseValue` handles numbers/bools too. `parseIdentifier` does NOT.

      # Better approach:
      # Reset pos
      p.pos = startPos
      # Check if identifier followed by =
      # We need a proper lookahead for keys.
      # For now, simplistic:

      let val = parseValue(p)
      # Check if we accidentally parsed a key?
      # If val is string, and next char is '=', convert to key?
      if val.kind == VString and p.peek() == '=':
        discard p.next()
        let realVal = parseValue(p)
        node.addProp(val.s, realVal)
      else:
        node.addArg(val)

  # Children
  skipSpace(p)
  if p.peek() == '{':
    discard p.next() # skip {
    while true:
      skipSpace(p)
      if p.peek() == '}':
        discard p.next()
        break
      skipSpace(p)
      # Skip newlines
      while p.peek() == '\n': discard p.next()
      if p.peek() == '}':
        discard p.next()
        break
      let child = parseNode(p)
      if child != nil:
        node.addChild(child)
      else:
        # Check if just newline?
        if p.peek() == '\n': discard p.next()
        else: break # Error or empty

  return node

proc parseKdl*(input: string): seq[Node] =
  var p = Parser(input: input, pos: 0)
  result = @[]
  while true:
    skipSpace(p)
    while p.peek() == '\n' or p.peek() == ';': discard p.next()
    if p.peek() == '\0': break

    let node = parseNode(p)
    if node != nil:
      result.add(node)
    else:
      break
