import std/strutils
import token, source_location, ast, lexer

type
  ParserDiagnosticSeverity* = enum
    pdsWarning
    pdsError

  ParserDiagnostic* = object
    severity*: ParserDiagnosticSeverity
    loc*: SourceLocation
    message*: string

  ParseResult* = object
    module*: Module
    diagnostics*: seq[ParserDiagnostic]

  Parser* = object
    tokens: seq[Token]
    sourceName: string
    pos: int
    diagnostics: seq[ParserDiagnostic]
    structInitAllowed: bool  ## disabled inside if/while/for/match conditions

proc initParser*(tokens: seq[Token], sourceName: string = "<input>"): Parser =
  result.tokens = tokens
  result.sourceName = sourceName
  result.pos = 0
  result.structInitAllowed = true

# ---------------------------------------------------------------------------
# Token helpers
# ---------------------------------------------------------------------------

proc peek(p: Parser, ahead: int = 0): TokenKind =
  let i = p.pos + ahead
  if i < p.tokens.len:
    return p.tokens[i].kind
  return tkEndOfFile

proc at(p: Parser): Token =
  if p.pos < p.tokens.len:
    return p.tokens[p.pos]
  return Token(kind: tkEndOfFile)

proc advance(p: var Parser): Token =
  result = p.at
  if p.pos < p.tokens.len:
    inc p.pos

proc hasErrors*(p: Parser): bool =
  for d in p.diagnostics:
    if d.severity == pdsError:
      return true
  return false

proc check(p: Parser, kind: TokenKind): bool =
  p.peek() == kind

proc checkAny(p: Parser, kinds: openArray[TokenKind]): bool =
  for k in kinds:
    if p.peek() == k: return true
  return false

proc isTypeArgListAhead(p: Parser): bool =
  ## Lookahead to determine if '<' starts a type argument list (`Foo<int>`).
  ## Returns true if we find a matching '>' before tokens that cannot appear
  ## inside type arguments. Must NOT match comparison `x < 0` when a later
  ## `x > 0` exists in the same expression/region (e.g. match arm guards).
  if not p.check(tkLt): return false
  var depth = 0
  var ahead = 0
  while true:
    let kind = p.peek(ahead)
    # Hard stops: cannot appear inside `<...>` type args
    if kind in {tkEndOfFile, tkLBrace, tkRBrace, tkSemicolon, tkFatArrow,
                tkIf, tkElse, tkWhile, tkFor, tkMatch, tkReturn, tkLet, tkVar,
                tkEq, tkNe, tkLe, tkGe, tkAmpAmp, tkPipePipe, tkAssign}:
      return false
    # Literals and comparison/arithmetic mean this is a value expression, not types
    if kind in {tkIntLiteral, tkFloatLiteral, tkStringLiteral, tkCharLiteral,
                tkBoolLiteral, tkPlus, tkMinus, tkSlash, tkPercent}:
      return false
    if kind == tkLt:
      inc depth
    elif kind == tkGt:
      dec depth
      if depth == 0:
        return true
      if depth < 0:
        return false
    inc ahead

proc expect(p: var Parser, kind: TokenKind, message: string): Token =
  if p.check(kind):
    return p.advance()
  let tok = p.at
  p.diagnostics.add(ParserDiagnostic(
    severity: pdsError,
    loc: tok.loc,
    message: message & " (got " & tokenKindName(tok.kind) & ")"
  ))
  result = tok

proc isKeywordToken(kind: TokenKind): bool =
  return kind in {tkIf, tkElse, tkWhile, tkDo, tkLoop, tkFor, tkIn, tkBreak,
    tkContinue, tkReturn, tkMatch, tkFunc, tkLet, tkVar, tkConst, tkType,
    tkStruct, tkEnum, tkUnion, tkInterface, tkExtend, tkModule, tkImport,
    tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkSizeOf, tkOwn,
    tkDiscard}

proc expectIdentOrKeyword(p: var Parser, message: string): Token =
  ## Accept identifier OR keyword token as a name (for field names, param names, etc.)
  if p.check(tkIdent) or isKeywordToken(p.at.kind):
    return p.advance()
  let tok = p.at
  p.diagnostics.add(ParserDiagnostic(
    severity: pdsError,
    loc: tok.loc,
    message: message & " (got " & tokenKindName(tok.kind) & ")"
  ))
  result = tok

proc previous(p: Parser): Token =
  if p.pos > 0 and p.pos <= p.tokens.len:
    return p.tokens[p.pos - 1]
  return Token(kind: tkEndOfFile)

proc currentLoc(p: Parser): SourceLocation =
  p.at.loc

proc emitError(p: var Parser, loc: SourceLocation, message: string) =
  p.diagnostics.add(ParserDiagnostic(severity: pdsError, loc: loc, message: message))

proc skipNewlines(p: var Parser) =
  while p.check(tkNewLine): discard p.advance()

proc emitError(p: var Parser, message: string) =
  p.emitError(p.currentLoc, message)

proc isAtEnd(p: Parser): bool =
  p.peek() == tkEndOfFile

# ---------------------------------------------------------------------------
# Recovery
# ---------------------------------------------------------------------------

proc synchronize(p: var Parser) =
  ## Skip tokens until a declaration boundary.
  discard p.advance()
  while not p.isAtEnd:
    if p.previous.kind == tkSemicolon: return
    case p.peek()
    of tkFunc, tkStruct, tkEnum, tkUnion, tkInterface, tkExtend,
       tkModule, tkImport, tkConst, tkType, tkExtern, tkPub:
      return
    else:
      discard p.advance()

# ---------------------------------------------------------------------------
# Forward declarations for mutual recursion
# ---------------------------------------------------------------------------

proc parseDecl(p: var Parser): Decl
proc parseType(p: var Parser): TypeExpr
proc parseExpr(p: var Parser): Expr
proc parseStmt(p: var Parser): Stmt
proc parseBlock(p: var Parser): Block
proc parsePattern(p: var Parser): Pattern

# ---------------------------------------------------------------------------
# Attributes
# ---------------------------------------------------------------------------

type
  ParsedAttrs* = object
    importLib*: string
    callConv*: CallingConvention
    targetOs*: string
    checked*: bool             ## @[Checked] — enable borrow checking
    shared*: bool              ## @[Shared] — mark function as thread-safe
    drop*: bool                ## @[Drop] — auto-call Type_Drop at scope exit
    release*: bool             ## @[Release] — explicit zero-cost (no borrow checks)

proc parseAttrs(p: var Parser): ParsedAttrs =
  while p.check(tkAt):
    discard p.advance()  # @
    discard p.expect(tkLBracket, "expected '[' after '@'")
    let name = p.expect(tkIdent, "expected attribute name").text
    if name == "Checked":
      result.checked = true
    elif name == "Shared":
      result.shared = true
    elif name == "Drop":
      result.drop = true
    elif name == "Release":
      result.release = true
    elif name == "Import":
      discard p.expect(tkLParen, "expected '('")
      let key = p.expect(tkIdent, "expected attribute key").text
      if key == "lib":
        discard p.expect(tkColon, "expected ':'")
        result.importLib = p.expect(tkStringLiteral, "expected string literal").text
      discard p.expect(tkRParen, "expected ')'")
    discard p.expect(tkRBracket, "expected ']'")

# ---------------------------------------------------------------------------
# Type expressions
# ---------------------------------------------------------------------------

proc parseBaseType(p: var Parser): TypeExpr =
  let loc = p.currentLoc
  case p.peek()
  of tkIdent:
    let name = p.advance().text
    if name == "self":
      return TypeExpr(kind: tekSelf, loc: loc)
    if p.check(tkLt):
      var typeArgs: seq[TypeExpr] = @[]
      discard p.advance()
      while not p.check(tkGt) and not p.isAtEnd:
        while p.check(tkNewLine):
          discard p.advance()
        if p.check(tkGt) or p.isAtEnd:
          break
        typeArgs.add(p.parseType())
        if p.check(tkComma):
          discard p.advance()
      discard p.expect(tkGt, "expected '>' to close type arguments")
      return TypeExpr(kind: tekNamed, loc: loc, typeName: name, typeArgs: typeArgs)
    return TypeExpr(kind: tekNamed, loc: loc, typeName: name)
  of tkOwn:
    discard p.advance()
    return TypeExpr(kind: tekOwn, loc: loc, pointerPointee: p.parseBaseType(), refLifetime: "")
  of tkStar:
    discard p.advance()
    return TypeExpr(kind: tekPointer, loc: loc, pointerPointee: p.parseBaseType(), refLifetime: "")
  of tkAmp:
    discard p.advance()
    var lt = ""
    if p.check(tkLifetime):
      lt = p.advance().text
    if p.check(tkMut):
      discard p.advance()
      return TypeExpr(kind: tekMutRef, loc: loc, refLifetime: lt, pointerPointee: p.parseBaseType())
    if p.check(tkDyn):
      discard p.advance()
      let ifaceName = p.expect(tkIdent, "expected interface name after 'dyn'").text
      return TypeExpr(kind: tekDynRef, loc: loc, dynInterface: ifaceName)
    return TypeExpr(kind: tekRef, loc: loc, refLifetime: lt, pointerPointee: p.parseBaseType())
  of tkLParen:
    discard p.advance()
    var elems: seq[TypeExpr] = @[]
    while not p.check(tkRParen) and not p.isAtEnd:
      elems.add(p.parseType())
      if p.check(tkComma):
        discard p.advance()
    discard p.expect(tkRParen, "expected ')' to close tuple type")
    return TypeExpr(kind: tekTuple, loc: loc, tupleElements: elems)
  of tkFunc:
    discard p.advance()  # func
    discard p.expect(tkLParen, "expected '(' after 'func'")
    var params: seq[TypeExpr] = @[]
    while not p.check(tkRParen) and not p.isAtEnd:
      params.add(p.parseType())
      if p.check(tkComma):
        discard p.advance()
    discard p.expect(tkRParen, "expected ')' to close func param types")
    var ret: TypeExpr = nil
    if p.check(tkArrow):
      discard p.advance()
      ret = p.parseType()
    return TypeExpr(kind: tekFunc, loc: loc, funcParams: params, funcRet: ret)
  else:
    p.emitError(loc, "expected type expression")
    return TypeExpr(kind: tekNamed, loc: loc, typeName: "")

proc parseType(p: var Parser): TypeExpr =
  var left = p.parseBaseType()
  while p.check(tkLBracket):
    let loc = p.currentLoc
    discard p.advance()
    if p.check(tkRBracket):
      discard p.advance()
      left = TypeExpr(kind: tekSlice, loc: loc, sliceElement: left, sliceSize: nil)
    else:
      let sizeExpr = p.parseExpr()
      discard p.expect(tkRBracket, "expected ']' to close array size")
      left = TypeExpr(kind: tekSlice, loc: loc, sliceElement: left, sliceSize: sizeExpr)
  # Path types: Std::Io::Reader
  while p.check(tkColonColon):
    discard p.advance()
    let nextSeg = p.expect(tkIdent, "expected identifier after '::'")
    if left.kind == tekNamed:
      var segs = @[left.typeName]
      segs.add(nextSeg.text)
      left = TypeExpr(kind: tekPath, loc: left.loc, pathSegments: segs)
    elif left.kind == tekPath:
      left.pathSegments.add(nextSeg.text)
  return left

# ---------------------------------------------------------------------------
# Patterns
# ---------------------------------------------------------------------------

proc parsePrimaryPattern(p: var Parser): Pattern =
  let loc = p.currentLoc
  case p.peek()
  of tkUnderscore:
    discard p.advance()
    return Pattern(kind: pkWildcard, loc: loc)
  of tkIntLiteral, tkFloatLiteral, tkStringLiteral, tkCharLiteral, tkBoolLiteral:
    return Pattern(kind: pkLiteral, loc: loc, patLit: p.advance())
  of tkIdent:
    let name = p.advance().text
    if name == "true" or name == "false":
      # These were lexed as BoolLiteral already, but just in case
      return Pattern(kind: pkLiteral, loc: loc, patLit: Token(kind: tkBoolLiteral, text: name, loc: loc))
    # Could be enum pattern or ident pattern
    if p.check(tkColonColon):
      var path = @[name]
      while p.check(tkColonColon):
        discard p.advance()
        path.add(p.expect(tkIdent, "expected identifier in pattern path").text)
      if p.check(tkLParen):
        discard p.advance()
        var args: seq[Pattern] = @[]
        var named: seq[tuple[name: string, pattern: Pattern]] = @[]
        while not p.check(tkRParen) and not p.isAtEnd:
          if p.check(tkIdent) and p.peek(1) == tkColon:
            let fieldName = p.advance().text
            discard p.advance()  # :
            named.add((fieldName, p.parsePattern()))
          else:
            args.add(p.parsePattern())
          if p.check(tkComma):
            discard p.advance()
        discard p.expect(tkRParen, "expected ')' to close enum pattern")
        return Pattern(kind: pkEnum, loc: loc, patEnumPath: path, patEnumArgs: args, patEnumNamed: named)
      return Pattern(kind: pkEnum, loc: loc, patEnumPath: path, patEnumArgs: @[], patEnumNamed: @[])
    elif p.check(tkLBrace):
      # Struct pattern: Point { x: px, y: py } or shorthand Point { x, y }
      discard p.advance()
      var fields: seq[tuple[name: string, pattern: Pattern]] = @[]
      while not p.check(tkRBrace) and not p.isAtEnd:
        while p.check(tkNewLine):
          discard p.advance()
        if p.check(tkRBrace) or p.isAtEnd:
          break
        let fieldName = p.expect(tkIdent, "expected field name in struct pattern").text
        if p.check(tkColon):
          discard p.advance()
          fields.add((fieldName, p.parsePattern()))
        else:
          # Shorthand: { x } means { x: x }
          fields.add((fieldName, Pattern(kind: pkIdent, loc: loc, patIdent: fieldName)))
        if p.check(tkComma):
          discard p.advance()
      discard p.expect(tkRBrace, "expected '}' to close struct pattern")
      return Pattern(kind: pkStruct, loc: loc, patStructName: name, patStructFields: fields)
    elif p.check(tkLParen):
      # Enum-like pattern with path of length 1
      discard p.advance()
      var args: seq[Pattern] = @[]
      while not p.check(tkRParen) and not p.isAtEnd:
        args.add(p.parsePattern())
        if p.check(tkComma):
          discard p.advance()
      discard p.expect(tkRParen, "expected ')' to close pattern")
      return Pattern(kind: pkEnum, loc: loc, patEnumPath: @[name], patEnumArgs: args)
    return Pattern(kind: pkIdent, loc: loc, patIdent: name)
  of tkLParen:
    discard p.advance()
    var elems: seq[Pattern] = @[]
    while not p.check(tkRParen) and not p.isAtEnd:
      elems.add(p.parsePattern())
      if p.check(tkComma):
        discard p.advance()
    discard p.expect(tkRParen, "expected ')' to close tuple pattern")
    return Pattern(kind: pkTuple, loc: loc, patTupleElements: elems)
  else:
    p.emitError(loc, "expected pattern")
    return Pattern(kind: pkWildcard, loc: loc)

proc parsePattern(p: var Parser): Pattern =
  let loc = p.currentLoc
  var left = p.parsePrimaryPattern()
  # Range pattern
  if p.check(tkDotDot) or p.check(tkDotDotEqual):
    let inclusive = p.check(tkDotDotEqual)
    discard p.advance()
    let right = p.parsePrimaryPattern()
    left = Pattern(kind: pkRange, loc: loc, patRangeLo: left, patRangeHi: right, patRangeInclusive: inclusive)
  # Guarded pattern: `p if cond` (also after range: `1..10 if x % 2 == 0`)
  if p.check(tkIf):
    discard p.advance()
    let guard = p.parseExpr()
    return Pattern(kind: pkGuarded, loc: loc, patGuardedInner: left, patGuardedExpr: guard)
  return left

# ---------------------------------------------------------------------------
# Expressions (Pratt / precedence climbing)
# ---------------------------------------------------------------------------

proc parsePrimary(p: var Parser): Expr
proc parsePostfix(p: var Parser): Expr
proc parseUnary(p: var Parser): Expr
proc parseExp(p: var Parser): Expr
proc parseMul(p: var Parser): Expr
proc parseAdd(p: var Parser): Expr
proc parseShift(p: var Parser): Expr
proc parseCast(p: var Parser): Expr
proc parseComparison(p: var Parser): Expr
proc parseRange(p: var Parser): Expr
proc parseEquality(p: var Parser): Expr
proc parseBitAnd(p: var Parser): Expr
proc parseBitXor(p: var Parser): Expr
proc parseBitOr(p: var Parser): Expr
proc parseAnd(p: var Parser): Expr
proc parseOr(p: var Parser): Expr
proc parseTernary(p: var Parser): Expr
proc parseAssign(p: var Parser): Expr

proc parseExpr(p: var Parser): Expr =
  p.parseAssign()

proc parseStringInterpolation(p: var Parser, tok: Token): Expr =
  ## Parse a string literal that contains {expr} interpolations.
  let text = tok.text
  # Only f"..." strings support interpolation; plain "...", backtick, and c8/c16/c32 are raw.
  if text.len < 3 or text[0] != 'f' or text[1] != '"' or text[^1] != '"':
    return newLiteralExpr(tok)
  let inner = text[2 ..< ^1]  # strip f" ... "
  var texts: seq[string] = @[]
  var exprs: seq[Expr] = @[]
  var i = 0
  var currentText = ""
  while i < inner.len:
    if inner[i] == '\\' and i + 1 < inner.len:
      if inner[i + 1] == '{':
        currentText.add('{')
        i += 2
        continue
      elif inner[i + 1] == '}':
        currentText.add('}')
        i += 2
        continue
    elif inner[i] == '{':
      texts.add(currentText)
      currentText = ""
      var j = i + 1
      var depth = 1
      while j < inner.len and depth > 0:
        if inner[j] == '{': depth += 1
        elif inner[j] == '}': depth -= 1
        j += 1
      if depth != 0:
        p.diagnostics.add(ParserDiagnostic(severity: pdsError, loc: tok.loc,
          message: "unmatched '{' in string interpolation"))
        return newLiteralExpr(tok)
      let exprStr = inner[i + 1 ..< j - 1]
      if exprStr.len == 0:
        p.diagnostics.add(ParserDiagnostic(severity: pdsError, loc: tok.loc,
          message: "empty interpolation {}"))
        return newLiteralExpr(tok)
      let lexRes = tokenize(exprStr, p.sourceName & "<interp>")
      if lexRes.hasErrors:
        p.diagnostics.add(ParserDiagnostic(severity: pdsError, loc: tok.loc,
          message: "invalid expression in string interpolation: " & exprStr))
        return newLiteralExpr(tok)
      var subParser = initParser(lexRes.tokens, p.sourceName & "<interp>")
      let expr = subParser.parseExpr()
      if subParser.hasErrors:
        for d in subParser.diagnostics:
          p.diagnostics.add(ParserDiagnostic(severity: pdsError, loc: tok.loc,
            message: "interpolation parse error: " & d.message))
        return newLiteralExpr(tok)
      exprs.add(expr)
      i = j
      continue
    currentText.add(inner[i])
    i += 1
  texts.add(currentText)
  if exprs.len == 0:
    # f"plain" / f"use \{x\}" with no real {expr} — use processed text (escapes applied)
    var litTok = tok
    litTok.text = "\"" & texts[0] & "\""
    return newLiteralExpr(litTok)
  return newStringInterpExpr(texts, exprs, tok.loc)

proc parsePrimary(p: var Parser): Expr =
  while p.check(tkNewLine):
    discard p.advance()
  let loc = p.currentLoc
  case p.peek()
  of tkIntLiteral, tkFloatLiteral, tkStringLiteral, tkCharLiteral, tkBoolLiteral:
    let tok = p.advance()
    if tok.kind == tkStringLiteral:
      return parseStringInterpolation(p, tok)
    return newLiteralExpr(tok)
  of tkSelf:
    discard p.advance()
    return Expr(kind: ekSelf, loc: loc)
  of tkIdent:
    let name = p.advance().text
    # Path expression: a::b::c
    if p.check(tkColonColon):
      var segs = @[name]
      while p.check(tkColonColon):
        discard p.advance()
        segs.add(p.expect(tkIdent, "expected identifier in path").text)
      return Expr(kind: ekPath, loc: loc, exprPath: segs)
    return newIdentExpr(name, loc)
  of tkLParen:
    discard p.advance()
    if p.check(tkRParen):
      discard p.advance()
      return Expr(kind: ekTuple, loc: loc, exprTupleElements: @[])
    let expr = p.parseExpr()
    if p.check(tkComma):
      # Tuple expression
      var elems = @[expr]
      while p.check(tkComma):
        discard p.advance()
        elems.add(p.parseExpr())
      discard p.expect(tkRParen, "expected ')' to close tuple")
      return Expr(kind: ekTuple, loc: loc, exprTupleElements: elems)
    discard p.expect(tkRParen, "expected ')'")
    return expr
  of tkLBrace:
    # Block expression
    let blk = p.parseBlock()
    return Expr(kind: ekBlock, loc: loc, exprBlock: blk)
  of tkLBracket:
    # Slice expression [a, b, c]
    discard p.advance()
    var elems: seq[Expr] = @[]
    while not p.check(tkRBracket) and not p.isAtEnd:
      elems.add(p.parseExpr())
      if p.check(tkComma):
        discard p.advance()
    discard p.expect(tkRBracket, "expected ']' to close slice")
    return Expr(kind: ekSlice, loc: loc, exprSliceElements: elems)
  of tkMatch:
    discard p.advance()
    p.structInitAllowed = false
    let subject = p.parseExpr()
    p.structInitAllowed = true
    # Same newline rules as statement-form match (needed for `let x = match ...`)
    while p.check(tkNewLine):
      discard p.advance()
    discard p.expect(tkLBrace, "expected '{' to start match")
    var arms: seq[MatchArm] = @[]
    while not p.check(tkRBrace) and not p.isAtEnd:
      while p.check(tkNewLine):
        discard p.advance()
      if p.check(tkRBrace) or p.isAtEnd:
        break
      let armLoc = p.currentLoc
      let pat = p.parsePattern()
      discard p.expect(tkFatArrow, "expected '=>' in match arm")
      let body = p.parseExpr()
      arms.add(MatchArm(loc: armLoc, pattern: pat, body: body))
      if p.check(tkComma):
        discard p.advance()
    discard p.expect(tkRBrace, "expected '}' to close match")
    return Expr(kind: ekMatch, loc: loc, exprMatchSubject: subject, exprMatchArms: arms)
  of tkMinus, tkBang, tkTilde, tkStar, tkAmp, tkPlusPlus, tkMinusMinus:
    # These are handled in parseUnary, but ++/-- as prefix are rare
    return p.parseUnary()
  of tkSizeOf:
    discard p.advance()  # sizeof
    discard p.expect(tkLParen, "expected '('")
    let ty = p.parseType()
    discard p.expect(tkRParen, "expected ')'")
    return Expr(kind: ekSizeOf, loc: loc, exprSizeOfType: ty)
  of tkSpawn:
    discard p.advance()  # spawn
    let callee = p.parsePrimary()
    var args: seq[Expr] = @[]
    if p.check(tkLParen):
      discard p.advance()  # (
      while not p.check(tkRParen) and not p.isAtEnd:
        args.add(p.parseExpr())
        if p.check(tkComma):
          discard p.advance()
      discard p.expect(tkRParen, "expected ')' after spawn arguments")
    return Expr(kind: ekSpawn, loc: loc, exprSpawnCallee: callee, exprSpawnArgs: args, exprSpawnAsync: false)
  of tkHashLine:
    discard p.advance()
    return Expr(kind: ekIntrinsic, loc: loc, exprIntrinsic: ikLine)
  of tkHashColumn:
    discard p.advance()
    return Expr(kind: ekIntrinsic, loc: loc, exprIntrinsic: ikColumn)
  of tkHashFile:
    discard p.advance()
    return Expr(kind: ekIntrinsic, loc: loc, exprIntrinsic: ikFile)
  of tkHashFunction:
    discard p.advance()
    return Expr(kind: ekIntrinsic, loc: loc, exprIntrinsic: ikFunction)
  of tkHashDate:
    discard p.advance()
    return Expr(kind: ekIntrinsic, loc: loc, exprIntrinsic: ikDate)
  of tkHashTime:
    discard p.advance()
    return Expr(kind: ekIntrinsic, loc: loc, exprIntrinsic: ikTime)
  of tkHashModule:
    discard p.advance()
    return Expr(kind: ekIntrinsic, loc: loc, exprIntrinsic: ikModule)
  of tkNull:
    discard p.advance()
    return newLiteralExpr(Token(kind: tkNull, text: "null", loc: loc))
  of tkPipePipe:
    # Empty-param closure written as `||` — lexer merges two '|' into tkPipePipe.
    # Disambiguate from logical-or (which only appears mid-expression): as a primary,
    # `|| -> T { ... }` / `|| { ... }` is always a zero-param closure.
    discard p.advance()  # ||
    var retTypePP: TypeExpr = nil
    if p.check(tkArrow):
      discard p.advance()  # ->
      retTypePP = p.parseType()
    let bodyPP = p.parseBlock()
    return Expr(kind: ekClosure, loc: loc, exprClosureParams: @[], exprClosureBody: bodyPP, exprClosureReturnType: retTypePP)
  of tkPipe:
    # Closure: |params| -> Ret { body }
    discard p.advance()  # |
    var params: seq[Param] = @[]
    while not p.check(tkPipe) and not p.isAtEnd:
      let nameTok = p.expect(tkIdent, "expected parameter name in closure")
      discard p.expect(tkColon, "expected ':' in closure parameter")
      let ptype = p.parseType()
      params.add(Param(name: nameTok.text, ptype: ptype))
      if p.check(tkComma):
        discard p.advance()
      else:
        break
    discard p.expect(tkPipe, "expected '|' to close closure params")
    var retType: TypeExpr = nil
    if p.check(tkArrow):
      discard p.advance()  # ->
      retType = p.parseType()
    let body = p.parseBlock()
    return Expr(kind: ekClosure, loc: loc, exprClosureParams: params, exprClosureBody: body, exprClosureReturnType: retType)
  else:
    p.emitError(loc, "expected expression")
    discard p.advance()
    return newLiteralExpr(Token(kind: tkIntLiteral, text: "0", loc: loc))

proc parsePostfix(p: var Parser): Expr =
  var left = p.parsePrimary()
  while true:
    while p.check(tkNewLine):
      discard p.advance()
    let loc = p.currentLoc
    case p.peek()
    of tkLParen:
      # Call expression
      discard p.advance()
      var args: seq[Expr] = @[]
      var argNames: seq[string] = @[]
      while not p.check(tkRParen) and not p.isAtEnd:
        if p.check(tkDotDotDot):
          discard p.advance()
          let operand = p.parseExpr()
          args.add(Expr(kind: ekSpread, loc: operand.loc, exprSpreadOperand: operand))
          argNames.add("")
        elif p.peek() == tkIdent and p.peek(1) == tkColon:
          # Named argument: name: value
          let nameTok = p.advance()
          discard p.advance()  # :
          let value = p.parseExpr()
          args.add(value)
          argNames.add(nameTok.text)
        else:
          args.add(p.parseExpr())
          argNames.add("")
        if p.check(tkComma):
          discard p.advance()
      discard p.expect(tkRParen, "expected ')' to close call")
      left = Expr(kind: ekCall, loc: loc, exprCallCallee: left, exprCallArgs: args, exprCallArgNames: argNames)
    of tkLt:
      # Generic type arguments: Max<int>(10, 20)
      # Only treat '<' as generic args if lookahead confirms a matching '>'
      if left.kind == ekIdent and p.isTypeArgListAhead():
        discard p.advance()
        var typeArgs: seq[TypeExpr] = @[]
        while not p.check(tkGt) and not p.isAtEnd:
          typeArgs.add(p.parseType())
          if p.check(tkComma):
            discard p.advance()
        discard p.expect(tkGt, "expected '>' to close type arguments")
        # Store type args in the identifier for later use
        left = Expr(kind: ekGenericCall, loc: loc, exprGenericCallee: left.exprIdent, exprGenericTypeArgs: typeArgs)
      else:
        break
    of tkLBracket:
      # Index expression
      discard p.advance()
      let idx = p.parseExpr()
      discard p.expect(tkRBracket, "expected ']' to close index")
      left = Expr(kind: ekIndex, loc: loc, exprIndexObj: left, exprIndexIdx: idx, exprIndexBoundsCheck: false)
    of tkDot:
      # Field expression, tuple index (.0, .1), or .await
      discard p.advance()
      if p.check(tkAwait):
        discard p.advance()
        left = Expr(kind: ekAwait, loc: loc, exprAwaitOperand: left)
      elif p.check(tkIntLiteral):
        # Tuple element access: t.0 → field "_0"
        let idxText = p.advance().text
        left = Expr(kind: ekField, loc: loc, exprFieldObj: left, exprFieldName: "_" & idxText)
      else:
        let fieldName = p.expectIdentOrKeyword("expected field name after '.'").text
        left = Expr(kind: ekField, loc: loc, exprFieldObj: left, exprFieldName: fieldName)
    of tkPlusPlus, tkMinusMinus:
      let op = p.advance().kind
      left = Expr(kind: ekPostfix, loc: loc, exprPostfixOp: op, exprPostfixOperand: left)
    of tkAs:
      discard p.advance()
      let ty = p.parseType()
      left = Expr(kind: ekCast, loc: loc, exprCastOperand: left, exprCastType: ty)
    of tkIs:
      discard p.advance()
      let ty = p.parseType()
      left = Expr(kind: ekIs, loc: loc, exprIsOperand: left, exprIsType: ty)
    of tkQuestion:
      discard p.advance()
      left = Expr(kind: ekTry, loc: loc, exprTryOperand: left, exprTryType: nil)
    of tkBang:
      discard p.advance()
      left = Expr(kind: ekUnwrap, loc: loc, exprUnwrapOperand: left)
    of tkLBrace:
      if p.structInitAllowed and left.kind in {ekIdent, ekPath, ekGenericCall}:
        discard p.advance()
        var fields: seq[tuple[name: string, value: Expr]] = @[]
        while not p.check(tkRBrace) and not p.isAtEnd:
          while p.check(tkNewLine): discard p.advance()
          if p.check(tkRBrace) or p.isAtEnd: break
          let fieldName = p.expect(tkIdent, "expected field name").text
          discard p.expect(tkColon, "expected ':'")
          let fieldValue = p.parseExpr()
          fields.add((fieldName, fieldValue))
          if p.check(tkComma):
            discard p.advance()
        discard p.expect(tkRBrace, "expected '}'")
        var typeName = ""
        var typeArgs: seq[TypeExpr] = @[]
        if left.kind == ekIdent:
          typeName = left.exprIdent
        elif left.kind == ekPath:
          typeName = left.exprPath.join("::")
        elif left.kind == ekGenericCall:
          typeName = left.exprGenericCallee
          typeArgs = left.exprGenericTypeArgs
        left = Expr(kind: ekStructInit, loc: loc, exprStructInitName: typeName,
                    exprStructInitTypeArgs: typeArgs, exprStructInitFields: fields)
      else:
        break
    else:
      break
  return left

proc parseUnary(p: var Parser): Expr =
  while p.check(tkNewLine):
    discard p.advance()
  let loc = p.currentLoc
  case p.peek()
  of tkBorrow:
    discard p.advance()  # borrow
    discard p.expect(tkAmp, "expected '&' after 'borrow'")
    var isMut = p.check(tkMut)
    if isMut:
      discard p.advance()  # mut
    let operand = p.parseUnary()  # parse the moved value
    return Expr(kind: ekBorrow, loc: loc, exprBorrowOperand: operand, exprBorrowMutable: isMut)
  of tkBang, tkMinus, tkTilde, tkStar, tkAmp:
    let op = p.advance().kind
    if op == tkAmp and p.check(tkMut):
      discard p.advance()  # mut
    let savedStructInit = p.structInitAllowed
    p.structInitAllowed = false
    let operand = p.parseUnary()
    p.structInitAllowed = savedStructInit
    return Expr(kind: ekUnary, loc: loc, exprUnaryOp: op, exprUnaryOperand: operand)
  of tkPlusPlus, tkMinusMinus:
    let op = p.advance().kind
    let operand = p.parseUnary()
    return Expr(kind: ekUnary, loc: loc, exprUnaryOp: op, exprUnaryOperand: operand)
  else:
    return p.parsePostfix()

proc parseExp(p: var Parser): Expr =
  let loc = p.currentLoc
  var left = p.parseUnary()
  while p.check(tkStarStar):
    let op = p.advance().kind
    let right = p.parseUnary()  # right-associative: parseUnary not parseExp
    left = newBinaryExpr(op, left, right, loc)
  return left

proc parseMul(p: var Parser): Expr =
  let loc = p.currentLoc
  var left = p.parseExp()
  while p.checkAny([tkStar, tkSlash, tkPercent]):
    let op = p.advance().kind
    let right = p.parseExp()
    left = newBinaryExpr(op, left, right, loc)
  return left

proc parseAdd(p: var Parser): Expr =
  let loc = p.currentLoc
  var left = p.parseMul()
  while p.checkAny([tkPlus, tkMinus]):
    let op = p.advance().kind
    let right = p.parseMul()
    left = newBinaryExpr(op, left, right, loc)
  return left

proc parseShift(p: var Parser): Expr =
  let loc = p.currentLoc
  var left = p.parseAdd()
  while p.checkAny([tkShl, tkShr]):
    let op = p.advance().kind
    let right = p.parseAdd()
    left = newBinaryExpr(op, left, right, loc)
  return left

proc parseCast(p: var Parser): Expr =
  var left = p.parseShift()
  # 'as' and 'is' are handled in postfix for chaining
  return left

proc parseComparison(p: var Parser): Expr =
  let loc = p.currentLoc
  var left = p.parseCast()
  while p.checkAny([tkLt, tkLe, tkGt, tkGe]):
    let op = p.advance().kind
    let right = p.parseCast()
    left = newBinaryExpr(op, left, right, loc)
  return left

proc parseRange(p: var Parser): Expr =
  let loc = p.currentLoc
  var left = p.parseComparison()
  if p.check(tkDotDot) or p.check(tkDotDotEqual):
    let inclusive = p.check(tkDotDotEqual)
    discard p.advance()
    let right = p.parseComparison()
    return Expr(kind: ekRange, loc: loc, exprRangeLo: left, exprRangeHi: right, exprRangeInclusive: inclusive)
  return left

proc parseEquality(p: var Parser): Expr =
  let loc = p.currentLoc
  var left = p.parseRange()
  while p.checkAny([tkEq, tkNe]):
    let op = p.advance().kind
    let right = p.parseRange()
    left = newBinaryExpr(op, left, right, loc)
  return left

proc parseBitAnd(p: var Parser): Expr =
  let loc = p.currentLoc
  var left = p.parseEquality()
  while p.check(tkAmp):
    let op = p.advance().kind
    let right = p.parseEquality()
    left = newBinaryExpr(op, left, right, loc)
  return left

proc parseBitXor(p: var Parser): Expr =
  let loc = p.currentLoc
  var left = p.parseBitAnd()
  while p.check(tkCaret):
    let op = p.advance().kind
    let right = p.parseBitAnd()
    left = newBinaryExpr(op, left, right, loc)
  return left

proc parseBitOr(p: var Parser): Expr =
  let loc = p.currentLoc
  var left = p.parseBitXor()
  while p.check(tkPipe):
    let op = p.advance().kind
    let right = p.parseBitXor()
    left = newBinaryExpr(op, left, right, loc)
  return left

proc parseAnd(p: var Parser): Expr =
  let loc = p.currentLoc
  var left = p.parseBitOr()
  p.skipNewlines()
  while p.check(tkAmpAmp):
    let op = p.advance().kind
    p.skipNewlines()
    let right = p.parseBitOr()
    left = newBinaryExpr(op, left, right, loc)
    p.skipNewlines()
  return left

proc parseOr(p: var Parser): Expr =
  let loc = p.currentLoc
  var left = p.parseAnd()
  p.skipNewlines()
  while p.check(tkPipePipe):
    let op = p.advance().kind
    p.skipNewlines()
    let right = p.parseAnd()
    left = newBinaryExpr(op, left, right, loc)
    p.skipNewlines()
  return left

proc parseTernary(p: var Parser): Expr =
  let loc = p.currentLoc
  var cond = p.parseOr()
  if p.check(tkQuestion):
    discard p.advance()
    let thenExpr = p.parseExpr()
    discard p.expect(tkColon, "expected ':' in ternary expression")
    let elseExpr = p.parseTernary()
    return Expr(kind: ekTernary, loc: loc, exprTernaryCond: cond, exprTernaryThen: thenExpr, exprTernaryElse: elseExpr)
  return cond

proc parseAssign(p: var Parser): Expr =
  let loc = p.currentLoc
  var left = p.parseTernary()
  if p.checkAny([tkAssign, tkPlusAssign, tkMinusAssign, tkStarAssign, tkSlashAssign,
                 tkPercentAssign, tkAmpAssign, tkPipeAssign, tkCaretAssign,
                 tkShlAssign, tkShrAssign]):
    let op = p.advance().kind
    let right = p.parseAssign()  # right-associative
    return Expr(kind: ekAssign, loc: loc, exprAssignOp: op, exprAssignTarget: left, exprAssignValue: right)
  return left

# ---------------------------------------------------------------------------
# Block
# ---------------------------------------------------------------------------

proc parseBlock(p: var Parser): Block =
  let loc = p.currentLoc
  discard p.expect(tkLBrace, "expected '{' to start block")
  result = newBlock(loc)
  while not p.check(tkRBrace) and not p.isAtEnd:
    while p.check(tkNewLine):
      discard p.advance()
    if p.check(tkRBrace) or p.isAtEnd:
      break
    result.stmts.add(p.parseStmt())
  discard p.expect(tkRBrace, "expected '}' to close block")

# ---------------------------------------------------------------------------
# Statements
# ---------------------------------------------------------------------------

proc parseStmt(p: var Parser): Stmt =
  let loc = p.currentLoc
  case p.peek()
  of tkLet, tkVar:
    let isMut = p.peek() == tkVar
    discard p.advance()
    let name = p.expect(tkIdent, "expected variable name").text
    var pat: Pattern = nil
    var ty: TypeExpr = nil
    if p.check(tkColon):
      discard p.advance()
      ty = p.parseType()
    var initExpr: Expr = nil
    if p.check(tkAssign):
      discard p.advance()
      initExpr = p.parseExpr()
    elif not isMut:
      discard p.expect(tkAssign, "expected '=' in let statement")
    if p.check(tkSemicolon):
      discard p.advance()
    return Stmt(kind: skLet, loc: loc, stmtLetMut: isMut, stmtLetName: name,
                stmtLetPattern: pat, stmtLetType: ty, stmtLetInit: initExpr)
  of tkIf:
    discard p.advance()
    p.structInitAllowed = false
    let cond = p.parseExpr()
    p.structInitAllowed = true
    let thenBlk = p.parseBlock()
    var elseIfs: seq[ElseIf] = @[]
    var elseBlk: Block = nil
    while p.check(tkNewLine): discard p.advance()
    while p.check(tkElse):
      let elseLoc = p.currentLoc
      discard p.advance()
      while p.check(tkNewLine): discard p.advance()
      if p.check(tkIf):
        discard p.advance()
        p.structInitAllowed = false
        let elifCond = p.parseExpr()
        p.structInitAllowed = true
        let elifBlk = p.parseBlock()
        elseIfs.add(ElseIf(loc: elseLoc, cond: elifCond, blk: elifBlk))
        while p.check(tkNewLine): discard p.advance()
      else:
        elseBlk = p.parseBlock()
        break
    return Stmt(kind: skIf, loc: loc, stmtIfCond: cond, stmtIfThen: thenBlk,
                stmtIfElseIfs: elseIfs, stmtIfElse: elseBlk)
  of tkWhile:
    discard p.advance()
    p.structInitAllowed = false
    let cond = p.parseExpr()
    p.structInitAllowed = true
    let body = p.parseBlock()
    return Stmt(kind: skWhile, loc: loc, stmtWhileCond: cond, stmtWhileBody: body)
  of tkDo:
    discard p.advance()
    let body = p.parseBlock()
    discard p.expect(tkWhile, "expected 'while' after 'do' block")
    let cond = p.parseExpr()
    if p.check(tkSemicolon):
      discard p.advance()
    return Stmt(kind: skDoWhile, loc: loc, stmtDoWhileBody: body, stmtDoWhileCond: cond)
  of tkLoop:
    discard p.advance()
    let body = p.parseBlock()
    return Stmt(kind: skLoop, loc: loc, stmtLoopBody: body)
  of tkFor:
    discard p.advance()
    let varName = p.expect(tkIdent, "expected loop variable name").text
    discard p.expect(tkIn, "expected 'in' in for loop")
    p.structInitAllowed = false
    let iter = p.parseExpr()
    p.structInitAllowed = true
    let body = p.parseBlock()
    return Stmt(kind: skFor, loc: loc, stmtForVar: varName, stmtForIter: iter, stmtForBody: body)
  of tkMatch:
    discard p.advance()
    p.structInitAllowed = false
    let subject = p.parseExpr()
    p.structInitAllowed = true
    # Skip newlines before opening brace
    while p.check(tkNewLine):
      discard p.advance()
    discard p.expect(tkLBrace, "expected '{' to start match")
    var arms: seq[MatchArm] = @[]
    while not p.check(tkRBrace) and not p.isAtEnd:
      # Skip newlines
      while p.check(tkNewLine):
        discard p.advance()
      if p.check(tkRBrace) or p.isAtEnd:
        break
      let armLoc = p.currentLoc
      let pat = p.parsePattern()
      discard p.expect(tkFatArrow, "expected '=>' in match arm")
      let body = p.parseExpr()
      arms.add(MatchArm(loc: armLoc, pattern: pat, body: body))
      if p.check(tkComma):
        discard p.advance()
    discard p.expect(tkRBrace, "expected '}' to close match")
    return Stmt(kind: skExpr, loc: loc, stmtExpr: Expr(kind: ekMatch, loc: loc, exprMatchSubject: subject, exprMatchArms: arms))
  of tkSwitch:
    discard p.advance()
    p.structInitAllowed = false
    let subject = p.parseExpr()
    p.structInitAllowed = true
    while p.check(tkNewLine):
      discard p.advance()
    discard p.expect(tkLBrace, "expected '{' to start switch body")
    var cases: seq[SwitchCase] = @[]
    var defaultBlock: Block = nil
    while not p.check(tkRBrace) and not p.isAtEnd:
      while p.check(tkNewLine):
        discard p.advance()
      if p.check(tkRBrace) or p.isAtEnd:
        break
      if p.check(tkDefault):
        discard p.advance()
        discard p.expect(tkColon, "expected ':' after default")
        defaultBlock = Block(loc: p.currentLoc, stmts: @[p.parseStmt()])
        continue
      if p.check(tkCase):
        discard p.advance()
        let caseVal = p.parseExpr()
        discard p.expect(tkColon, "expected ':' after case value")
        let caseBody = Block(loc: p.currentLoc, stmts: @[p.parseStmt()])
        cases.add(SwitchCase(loc: caseVal.loc, caseValue: caseVal, caseBody: caseBody))
        continue
      # Unknown token in switch body — skip
      discard p.advance()
    discard p.expect(tkRBrace, "expected '}' to close switch")
    return Stmt(kind: skSwitch, loc: loc, stmtSwitchExpr: subject, stmtSwitchCases: cases, stmtSwitchDefault: defaultBlock)
  of tkReturn:
    discard p.advance()
    var val: Expr = nil
    if not p.check(tkSemicolon) and not p.check(tkRBrace):
      val = p.parseExpr()
    if p.check(tkSemicolon):
      discard p.advance()
    return Stmt(kind: skReturn, loc: loc, stmtReturnValue: val)
  of tkBreak:
    discard p.advance()
    var label = ""
    if p.check(tkIdent):
      label = p.advance().text
    if p.check(tkSemicolon):
      discard p.advance()
    return Stmt(kind: skBreak, loc: loc, stmtBreakLabel: label)
  of tkContinue:
    discard p.advance()
    var label = ""
    if p.check(tkIdent):
      label = p.advance().text
    if p.check(tkSemicolon):
      discard p.advance()
    return Stmt(kind: skContinue, loc: loc, stmtContinueLabel: label)
  of tkStaticAssert:
    discard p.advance()
    let cond = p.parseExpr()
    var msg: Expr = nil
    if p.check(tkComma):
      discard p.advance()
      msg = p.parseExpr()
    if p.check(tkSemicolon):
      discard p.advance()
    return Stmt(kind: skStaticAssert, loc: loc, stmtStaticAssertCond: cond, stmtStaticAssertMsg: msg)
  of tkComptime:
    discard p.advance()
    let blk = p.parseBlock()
    return Stmt(kind: skComptime, loc: loc, stmtComptimeBlock: blk)
  of tkHashEmit:
    discard p.advance()
    let expr = p.parseExpr()
    if p.check(tkSemicolon):
      discard p.advance()
    return Stmt(kind: skEmit, loc: loc, stmtEmitExpr: expr, stmtEmitEvaluated: "")
  of tkDiscard:
    discard p.advance()
    var val: Expr = nil
    if not p.check(tkSemicolon) and not p.check(tkRBrace) and not p.check(tkNewLine):
      val = p.parseExpr()
    if p.check(tkSemicolon):
      discard p.advance()
    # discard expr → expression statement; discard; → no-op (nil expr)
    if val != nil:
      return Stmt(kind: skExpr, loc: loc, stmtExpr: val)
    else:
      # No-op: emit literal 0 as expression statement
      let zeroTok = Token(kind: tkIntLiteral, text: "0", loc: loc)
      return Stmt(kind: skExpr, loc: loc, stmtExpr: Expr(kind: ekLiteral, loc: loc, exprLit: zeroTok))
  of tkDefer:
    discard p.advance()
    let expr = p.parseExpr()
    if p.check(tkSemicolon):
      discard p.advance()
    return Stmt(kind: skDefer, loc: loc, stmtDeferBody: expr)
  of tkFunc, tkStruct, tkEnum, tkUnion, tkInterface, tkExtend, tkModule,
     tkImport, tkConst, tkType, tkExtern, tkPub:
    # Local declaration
    let decl = p.parseDecl()
    return Stmt(kind: skDecl, loc: loc, stmtDecl: decl)
  else:
    # Expression statement
    let expr = p.parseExpr()
    if p.check(tkSemicolon):
      discard p.advance()
    return Stmt(kind: skExpr, loc: loc, stmtExpr: expr)

# ---------------------------------------------------------------------------
# Declarations
# ---------------------------------------------------------------------------

proc parseTypeParams(p: var Parser): seq[TypeParam] =
  if p.check(tkLt):
    discard p.advance()
    while not p.check(tkGt) and not p.isAtEnd:
      while p.check(tkNewLine):
        discard p.advance()
      if p.check(tkGt) or p.isAtEnd:
        break
      var name = ""
      var isLifetime = false
      if p.check(tkIdent):
        name = p.advance().text
      elif p.check(tkLifetime):
        name = p.advance().text
        isLifetime = true
      else:
        discard p.expect(tkIdent, "expected type parameter name")
      var bounds: seq[string] = @[]
      if p.check(tkColon):
        discard p.advance()
        # Parse bound: single identifier or path like Std::Comparable
        var boundName = ""
        while true:
          let part = p.expect(tkIdent, "expected trait/interface name").text
          if boundName.len > 0:
            boundName.add("_")
          boundName.add(part)
          if p.check(tkColonColon):
            discard p.advance()
          else:
            break
        bounds.add(boundName)
      result.add(TypeParam(name: name, bounds: bounds, isLifetime: isLifetime))
      if p.check(tkComma):
        discard p.advance()
    discard p.expect(tkGt, "expected '>' to close type parameters")

proc parseParamList(p: var Parser, allowVariadic: bool = false): seq[Param] =
  discard p.expect(tkLParen, "expected '('")
  while not p.check(tkRParen) and not p.isAtEnd:
    while p.check(tkNewLine):
      discard p.advance()
    if p.check(tkRParen) or p.isAtEnd:
      break
    let loc = p.currentLoc
    if allowVariadic and p.check(tkDotDotDot):
      discard p.advance()
      let nameTok = p.at
      var name = ""
      if nameTok.kind == tkIdent:
        name = p.advance().text
      elif nameTok.kind == tkSelf:
        name = p.advance().text
      else:
        name = p.expect(tkIdent, "expected parameter name after '...'").text
      let ty = p.parseType()
      result.add(Param(loc: loc, name: name, ptype: ty, isVariadic: true))
    else:
      let nameTok = p.at
      var name = ""
      if nameTok.kind == tkIdent:
        name = p.advance().text
      elif nameTok.kind == tkSelf:
        name = p.advance().text
      else:
        name = p.expect(tkIdent, "expected parameter name").text
      discard p.expect(tkColon, "expected ':' after parameter name")
      let ty = p.parseType()
      var defaultVal: Expr = nil
      if p.check(tkAssign):
        discard p.advance()
        defaultVal = p.parseExpr()
      result.add(Param(loc: loc, name: name, ptype: ty, defaultValue: defaultVal))
    if p.check(tkComma):
      discard p.advance()
  discard p.expect(tkRParen, "expected ')' to close parameter list")

proc parseFuncDecl(p: var Parser, isPublic: bool, isAsm: bool, attrs: ParsedAttrs, isConst: bool = false, isAsync: bool = false): Decl =
  let loc = p.currentLoc
  discard p.expect(tkFunc, "expected 'func'")
  let name = p.expect(tkIdent, "expected function name").text
  let typeParams = p.parseTypeParams()
  let params = p.parseParamList(true)
  var retType: TypeExpr = nil
  if p.check(tkArrow):
    discard p.advance()
    retType = p.parseType()
  var body: Block = nil
  if p.check(tkLBrace):
    body = p.parseBlock()
  elif p.check(tkSemicolon):
    discard p.advance()
  var declAttrs: seq[string] = @[]
  if attrs.checked: declAttrs.add("Checked")
  if attrs.release: declAttrs.add("Release")
  if attrs.drop: declAttrs.add("Drop")
  return Decl(kind: dkFunc, loc: loc, isPublic: isPublic,
              declAttrs: declAttrs,
              declFuncAsm: isAsm, declFuncCallConv: attrs.callConv,
              declFuncConst: isConst, declFuncIsAsync: isAsync,
              declFuncName: name, declFuncTypeParams: typeParams,
              declFuncParams: params, declFuncReturnType: retType,
              declFuncBody: body)

proc parseStructDecl(p: var Parser, isPublic: bool, attrs: ParsedAttrs = ParsedAttrs()): Decl =
  let loc = p.currentLoc
  discard p.expect(tkStruct, "expected 'struct'")
  let name = p.expect(tkIdent, "expected struct name").text
  let typeParams = p.parseTypeParams()
  discard p.expect(tkLBrace, "expected '{' to start struct body")
  var fields: seq[StructField] = @[]
  while not p.check(tkRBrace) and not p.isAtEnd:
    # Skip newlines
    while p.check(tkNewLine):
      discard p.advance()
    if p.check(tkRBrace) or p.isAtEnd:
      break
    let startPos = p.pos  # Track position for infinite-loop safeguard
    let fLoc = p.currentLoc
    var fPub = false
    if p.check(tkPub):
      fPub = true
      discard p.advance()
    let fName = p.expectIdentOrKeyword("expected field name").text
    discard p.expect(tkColon, "expected ':' after field name")
    let fType = p.parseType()
    if p.check(tkSemicolon) or p.check(tkComma):
      discard p.advance()
    fields.add(StructField(loc: fLoc, isPublic: fPub, name: fName, ftype: fType))
    # Infinite-loop safeguard: if no progress, advance
    if p.pos == startPos:
      discard p.advance()
  discard p.expect(tkRBrace, "expected '}' to close struct")
  var declAttrs: seq[string] = @[]
  if attrs.drop: declAttrs.add("Drop")
  if attrs.checked: declAttrs.add("Checked")
  return Decl(kind: dkStruct, loc: loc, isPublic: isPublic, declAttrs: declAttrs,
              declStructName: name, declStructTypeParams: typeParams,
              declStructFields: fields)

proc parseEnumDecl(p: var Parser, isPublic: bool): Decl =
  let loc = p.currentLoc
  discard p.expect(tkEnum, "expected 'enum'")
  let name = p.expect(tkIdent, "expected enum name").text
  var baseType: TypeExpr = nil
  if p.check(tkColon):
    discard p.advance()
    baseType = p.parseType()
  discard p.expect(tkLBrace, "expected '{' to start enum body")
  var variants: seq[EnumVariant] = @[]
  while not p.check(tkRBrace) and not p.isAtEnd:
    # Skip newlines
    while p.check(tkNewLine):
      discard p.advance()
    if p.check(tkRBrace) or p.isAtEnd:
      break
    let vLoc = p.currentLoc
    let vName = p.expect(tkIdent, "expected variant name").text
    var fields: seq[TypeExpr] = @[]
    var namedFields: seq[tuple[name: string, ftype: TypeExpr]] = @[]
    var discr: string = ""
    if p.check(tkLParen):
      discard p.advance()
      while not p.check(tkRParen) and not p.isAtEnd:
        if p.check(tkIdent) and p.peek(1) == tkColon:
          let fn = p.advance().text
          discard p.advance()  # :
          let ft = p.parseType()
          namedFields.add((fn, ft))
        else:
          fields.add(p.parseType())
        if p.check(tkComma):
          discard p.advance()
      discard p.expect(tkRParen, "expected ')' to close variant")
    if p.check(tkAssign):
      discard p.advance()
      discr = p.expect(tkIdent, "expected discriminant").text
    if p.check(tkComma):
      discard p.advance()
    variants.add(EnumVariant(loc: vLoc, name: vName, fields: fields,
                             namedFields: namedFields, discriminant: discr))
  discard p.expect(tkRBrace, "expected '}' to close enum")
  return Decl(kind: dkEnum, loc: loc, isPublic: isPublic,
              declEnumName: name, declEnumBaseType: baseType,
              declEnumVariants: variants)

proc parseUnionDecl(p: var Parser, isPublic: bool): Decl =
  let loc = p.currentLoc
  discard p.expect(tkUnion, "expected 'union'")
  let name = p.expect(tkIdent, "expected union name").text
  discard p.expect(tkLBrace, "expected '{' to start union body")
  var fields: seq[UnionField] = @[]
  while not p.check(tkRBrace) and not p.isAtEnd:
    let fLoc = p.currentLoc
    let fName = p.expect(tkIdent, "expected field name").text
    discard p.expect(tkColon, "expected ':' after field name")
    let fType = p.parseType()
    if p.check(tkSemicolon):
      discard p.advance()
    fields.add(UnionField(loc: fLoc, name: fName, ftype: fType))
  discard p.expect(tkRBrace, "expected '}' to close union")
  return Decl(kind: dkUnion, loc: loc, isPublic: isPublic,
              declUnionName: name, declUnionFields: fields)

proc parseInterfaceDecl(p: var Parser, isPublic: bool): Decl =
  let loc = p.currentLoc
  discard p.expect(tkInterface, "expected 'interface'")
  let name = p.expect(tkIdent, "expected interface name").text
  discard p.expect(tkLBrace, "expected '{' to start interface body")
  var methods: seq[Decl] = @[]
  var assocTypes: seq[string] = @[]
  while not p.check(tkRBrace) and not p.isAtEnd:
    # Skip newlines
    while p.check(tkNewLine):
      discard p.advance()
    if p.check(tkRBrace) or p.isAtEnd:
      break
    if p.check(tkType):
      discard p.advance()
      let assocName = p.expect(tkIdent, "expected associated type name").text
      if p.check(tkSemicolon):
        discard p.advance()
      assocTypes.add(assocName)
    else:
      methods.add(p.parseFuncDecl(false, false, ParsedAttrs()))
  discard p.expect(tkRBrace, "expected '}' to close interface")
  return Decl(kind: dkInterface, loc: loc, isPublic: isPublic,
              declInterfaceName: name, declInterfaceAssocTypes: assocTypes,
              declInterfaceMethods: methods)

proc parseImplDecl(p: var Parser): Decl =
  let loc = p.currentLoc
  discard p.expect(tkExtend, "expected 'extend'")
  let typeName = p.expect(tkIdent, "expected type name").text
  let typeParams = p.parseTypeParams()
  var interfaceName = ""
  if p.check(tkFor):
    discard p.advance()
    interfaceName = p.expect(tkIdent, "expected interface name").text
  discard p.expect(tkLBrace, "expected '{' to start impl block")
  var methods: seq[Decl] = @[]
  var assocTypes: seq[tuple[name: string, typ: TypeExpr]] = @[]
  while not p.check(tkRBrace) and not p.isAtEnd:
    # Skip newlines
    while p.check(tkNewLine):
      discard p.advance()
    if p.check(tkRBrace) or p.isAtEnd:
      break
    if p.check(tkType):
      discard p.advance()
      let assocName = p.expect(tkIdent, "expected associated type name").text
      discard p.expect(tkAssign, "expected '=' in associated type implementation")
      let assocType = p.parseType()
      if p.check(tkSemicolon):
        discard p.advance()
      assocTypes.add((assocName, assocType))
    else:
      methods.add(p.parseFuncDecl(false, false, ParsedAttrs()))
  discard p.expect(tkRBrace, "expected '}' to close impl block")
  return Decl(kind: dkImpl, loc: loc, declImplTypeName: typeName,
              declImplTypeParams: typeParams,
              declImplInterface: interfaceName,
              declImplAssocTypes: assocTypes,
              declImplMethods: methods)

proc parseModuleDecl(p: var Parser, isPublic: bool): Decl =
  let loc = p.currentLoc
  discard p.expect(tkModule, "expected 'module'")
  var path: seq[string] = @[]
  path.add(p.expect(tkIdent, "expected module name").text)
  while p.check(tkColonColon):
    discard p.advance()
    path.add(p.expect(tkIdent, "expected module path segment").text)
  let name = path[^1]
  discard p.expect(tkLBrace, "expected '{' to start module body")
  var items: seq[Decl] = @[]
  while not p.check(tkRBrace) and not p.isAtEnd:
    while p.check(tkNewLine):
      discard p.advance()
    if p.check(tkRBrace) or p.isAtEnd:
      break
    items.add(p.parseDecl())
  discard p.expect(tkRBrace, "expected '}' to close module")
  return Decl(kind: dkModule, loc: loc, isPublic: isPublic,
              declModuleName: name, declModulePath: path,
              declModuleItems: items)

proc parseUseDecl(p: var Parser, attrs: ParsedAttrs): Decl =
  let loc = p.currentLoc
  discard p.expect(tkImport, "expected 'import'")
  var path: seq[string] = @[]
  path.add(p.expect(tkIdent, "expected import path segment").text)
  while p.check(tkColonColon):
    # Lookahead: if :: is followed by { or *, don't consume it here
    if p.peek(1) == tkLBrace or p.peek(1) == tkStar:
      break
    discard p.advance()
    path.add(p.expect(tkIdent, "expected import path segment").text)
  var kind = ukSingle
  var names: seq[string] = @[]
  if p.check(tkDotDotDot):
    discard p.advance()
    kind = ukGlob
  elif p.check(tkDot):
    discard p.advance()
    discard p.expect(tkStar, "expected '*'")
    kind = ukGlob
  elif p.check(tkColonColon):
    discard p.advance()
    if p.check(tkLBrace):
      discard p.advance()
      while not p.check(tkRBrace) and not p.isAtEnd:
        while p.check(tkNewLine):
          discard p.advance()
        if p.check(tkRBrace) or p.isAtEnd:
          break
        names.add(p.expect(tkIdent, "expected name in multi-import").text)
        if p.check(tkComma):
          discard p.advance()
      discard p.expect(tkRBrace, "expected '}' to close multi-import")
      kind = ukMulti
    elif p.check(tkStar):
      discard p.advance()
      kind = ukGlob
  if p.check(tkSemicolon):
    discard p.advance()
  return Decl(kind: dkUse, loc: loc, declUsePath: path, declUseKind: kind,
              declUseNames: names, declUseTargetOs: attrs.targetOs)

proc parseConstDecl(p: var Parser, isPublic: bool): Decl =
  let loc = p.currentLoc
  discard p.expect(tkConst, "expected 'const'")
  let name = p.expect(tkIdent, "expected constant name").text
  var ty: TypeExpr = nil
  if p.check(tkColon):
    discard p.advance()
    ty = p.parseType()
  discard p.expect(tkAssign, "expected '=' in const declaration")
  let value = p.parseExpr()
  if p.check(tkSemicolon):
    discard p.advance()
  return Decl(kind: dkConst, loc: loc, isPublic: isPublic,
              declConstName: name, declConstType: ty, declConstValue: value)

proc parseTypeAliasDecl(p: var Parser, isPublic: bool): Decl =
  let loc = p.currentLoc
  discard p.expect(tkType, "expected 'type'")
  let name = p.expect(tkIdent, "expected type alias name").text
  discard p.expect(tkAssign, "expected '=' in type alias")
  let ty = p.parseType()
  if p.check(tkSemicolon):
    discard p.advance()
  return Decl(kind: dkTypeAlias, loc: loc, isPublic: isPublic,
              declAliasName: name, declAliasType: ty)

proc parseExternDecl(p: var Parser, isPublic: bool, attrs: ParsedAttrs): Decl =
  let loc = p.currentLoc
  discard p.expect(tkExtern, "expected 'extern'")
  if p.check(tkFunc):
    let funcDecl = p.parseFuncDecl(isPublic, false, attrs)
    # Convert dkFunc to dkExternFunc
    return Decl(kind: dkExternFunc, loc: funcDecl.loc, isPublic: isPublic,
                declExtFuncName: funcDecl.declFuncName,
                declExtFuncDll: attrs.importLib,
                declExtFuncCallConv: attrs.callConv,
                declExtFuncParams: funcDecl.declFuncParams,
                declExtFuncVariadic: false,
                declExtFuncReturnType: funcDecl.declFuncReturnType)
  elif p.check(tkLBrace):
    discard p.advance()
    var items: seq[Decl] = @[]
    while not p.check(tkRBrace) and not p.isAtEnd:
      if p.check(tkFunc):
        items.add(p.parseFuncDecl(isPublic, false, attrs))
      elif p.check(tkIdent):
        let vName = p.advance().text
        discard p.expect(tkColon, "expected ':'")
        let vType = p.parseType()
        if p.check(tkSemicolon):
          discard p.advance()
        items.add(Decl(kind: dkExternVar, loc: loc, isPublic: isPublic,
                       declExtVarName: vName, declExtVarType: vType))
      else:
        p.emitError("expected function or variable in extern block")
        discard p.advance()
    discard p.expect(tkRBrace, "expected '}' to close extern block")
    return Decl(kind: dkExternBlock, loc: loc, declExtBlockDll: attrs.importLib,
                declExtBlockCallConv: attrs.callConv, declExtBlockItems: items)
  else:
    # Single extern variable
    let vName = p.expect(tkIdent, "expected variable name").text
    discard p.expect(tkColon, "expected ':'")
    let vType = p.parseType()
    if p.check(tkSemicolon):
      discard p.advance()
    return Decl(kind: dkExternVar, loc: loc, isPublic: isPublic,
                declExtVarName: vName, declExtVarType: vType)

proc parseDecl(p: var Parser): Decl =
  let loc = p.currentLoc
  var isPublic = false
  if p.check(tkPub):
    isPublic = true
    discard p.advance()
  
  var attrs = ParsedAttrs()
  if p.check(tkAt):
    attrs = p.parseAttrs()
  p.skipNewlines()
  
  var isConst = false
  if p.check(tkConst) and p.peek(1) == tkFunc:
    isConst = true
    discard p.advance()

  var isAsync = false
  if p.check(tkAsync) and p.peek(1) == tkFunc:
    isAsync = true
    discard p.advance()
  
  case p.peek()
  of tkFunc:
    return p.parseFuncDecl(isPublic, false, attrs, isConst, isAsync)
  of tkStruct:
    return p.parseStructDecl(isPublic, attrs)
  of tkEnum:
    return p.parseEnumDecl(isPublic)
  of tkUnion:
    return p.parseUnionDecl(isPublic)
  of tkInterface:
    return p.parseInterfaceDecl(isPublic)
  of tkExtend:
    return p.parseImplDecl()
  of tkModule:
    return p.parseModuleDecl(isPublic)
  of tkImport:
    return p.parseUseDecl(attrs)
  of tkConst:
    return p.parseConstDecl(isPublic)
  of tkType:
    return p.parseTypeAliasDecl(isPublic)
  of tkExtern:
    return p.parseExternDecl(isPublic, attrs)
  else:
    p.emitError(loc, "expected declaration")
    p.synchronize()
    return Decl(kind: dkFunc, loc: loc, declFuncName: "")

# ---------------------------------------------------------------------------
# Module (top-level)
# ---------------------------------------------------------------------------

proc parseModule*(p: var Parser, name: string): ParseResult =
  var modu = newModule(name)
  while not p.isAtEnd:
    if p.check(tkNewLine):
      discard p.advance()
      continue
    modu.items.add(p.parseDecl())
  result = ParseResult(module: modu, diagnostics: p.diagnostics)

proc parse*(tokens: seq[Token], sourceName: string = "<input>"): ParseResult =
  var p = initParser(tokens, sourceName)
  result = p.parseModule(sourceName)
