// ============================================================
// MoonbitJinja — Parser
// ============================================================

///|
priv struct VarPath {
  parts : Array[String]
}

///|
priv struct Call {
  name : String
  args : Array[Expr]
  kwargs : Map[String, Expr]
}

///|
priv struct MacroParam {
  name : String
  default : Expr?
}

// ==================== Operators ====================

///|
priv enum BinOp {
  Add
  Sub
  Mul
  Div
  FloorDiv
  Mod
  Pow
  Concat
  Eq
  Ne
  Lt
  Le
  Gt
  Ge
  And
  Or
  In
  NotIn
} derive(Eq)

///|
fn op_to_binop(op : String) -> BinOp? {
  match op {
    "+" => Some(BinOp::Add)
    "-" => Some(BinOp::Sub)
    "*" => Some(BinOp::Mul)
    "/" => Some(BinOp::Div)
    "//" => Some(BinOp::FloorDiv)
    "%" => Some(BinOp::Mod)
    "**" => Some(BinOp::Pow)
    "~" => Some(BinOp::Concat)
    "==" => Some(BinOp::Eq)
    "!=" => Some(BinOp::Ne)
    "<" => Some(BinOp::Lt)
    "<=" => Some(BinOp::Le)
    ">" => Some(BinOp::Gt)
    ">=" => Some(BinOp::Ge)
    "and" => Some(BinOp::And)
    "or" => Some(BinOp::Or)
    "in" => Some(BinOp::In)
    "not in" => Some(BinOp::NotIn)
    _ => None
  }
}

///|
fn precedence(op : BinOp) -> Int {
  match op {
    BinOp::Pow => 80
    BinOp::Mul | BinOp::Div | BinOp::FloorDiv | BinOp::Mod => 70
    BinOp::Add | BinOp::Sub | BinOp::Concat => 60
    BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge | BinOp::In | BinOp::NotIn =>
      50
    BinOp::Eq | BinOp::Ne => 40
    BinOp::And => 30
    BinOp::Or => 20
  }
}

///|
priv enum UnOp {
  Not
  Neg
  Pos
}

// ==================== AST ====================

///|
priv enum Expr {
  TextNode(String, Bool, Bool)
  RawText(String)
  Var(VarPath)
  Literal(Value)
  ListLiteral(Array[Expr])
  MapLiteral(Array[(String, Expr)])
  Unary(UnOp, Expr)
  Binary(Expr, BinOp, Expr)
  Conditional(Expr, Expr, Expr?)
  Index(Expr, Expr)
  TestCall(Expr, String, Bool, Array[Expr], Map[String, Expr])
  FunctionCall(Call)
  FilterChain(Expr, Array[Call])
  IfBlock(Expr, Array[Expr], Array[Expr])
  ForBlock(String, Expr, Array[Expr], Array[Expr])
  IncludeNode(String, Bool)
  BlockNode(String, Array[Expr])
  SetStmt(String, Expr)
  WithBlock(Map[String, Expr], Array[Expr])
  MacroDef(String, Array[MacroParam], Array[Expr])
  CallBlock(Call, Array[Expr])
  ImportNode(String, String)
  FromImportNode(String, Array[String])
  ExtendsNode(String)
  Break
  Continue
}

// 顶层模板(用于继承支持)

///|
priv struct Template {
  parent : String?
  prefix : Array[Expr]
  body : Array[Expr]
}

///|
fn is_block_start(t : Token) -> Bool {
  match t {
    BlockStart(_) => true
    _ => false
  }
}

///|
fn is_block_end(t : Token) -> Bool {
  match t {
    BlockEnd(_) => true
    _ => false
  }
}

///|
fn is_var_end(t : Token) -> Bool {
  match t {
    VarEnd(_) => true
    _ => false
  }
}

///|
///调试用,
fn parse_fail(
  _i : Ref[Int],
  _tokens : Array[Token],
  _len : Int,
  msg : String,
) -> String raise JinjaError {
  //let from = if i.val >= 3 { i.val - 3 } else { 0 }
  //let to = if i.val + 3 < len { i.val + 3 } else { len - 1 }

  // 收集并拼成一行
  //let near : Array[String] = []
  //for k in from..=to {
  //  near.push(token_to_string(tokens[k]))
  //}
  ////println("PARSE ERROR at index=\{i.val}")
  ////println("NEAR TOKENS: [" + near.join(", ") + "]")
  raise ParseError(msg + " at token " + _i.val.to_string())
}

// ==================== Parsing Entry ====================

///|
fn parse(
  stream : TokenStream,
  max_depth? : Int = 256,
  enable_macros? : Bool = true,
  enable_multi_template? : Bool = true,
  enable_loop_controls? : Bool = true,
) -> Template raise JinjaError {
  let tokens = stream.tokens
  let i : Ref[Int] = { val: 0 }
  let len = tokens.length()
  try {
    validate_parse_depth(tokens, max_depth)
    let tpl = parse_template(tokens, i, len)
    validate_control_flow(tpl.body, 0)
    validate_unique_blocks(tpl.body, Map([]))
    validate_features(
      tpl, enable_macros, enable_multi_template, enable_loop_controls,
    )
    tpl
  } catch {
    ParseError(message) => {
      let span = stream.spans[if i.val < stream.spans.length() {
          i.val
        } else {
          stream.spans.length() - 1
        }]
      raise ParseError(
        message +
        " at " +
        span.line.to_string() +
        ":" +
        span.column.to_string() +
        " (bytes " +
        span.start.to_string() +
        ".." +
        span.end.to_string() +
        ")",
      )
    }
    error => raise error
  }
}

///|
fn validate_features(
  template : Template,
  enable_macros : Bool,
  enable_multi_template : Bool,
  enable_loop_controls : Bool,
) -> Unit raise JinjaError {
  if !enable_multi_template && template.parent is Some(_) {
    raise ParseError("Template inheritance is disabled")
  }
  validate_node_features(
    template.body,
    enable_macros,
    enable_multi_template,
    enable_loop_controls,
  )
}

///|
fn validate_node_features(
  nodes : Array[Expr],
  enable_macros : Bool,
  enable_multi_template : Bool,
  enable_loop_controls : Bool,
) -> Unit raise JinjaError {
  for node in nodes {
    match node {
      MacroDef(_, _, _) | CallBlock(_, _) if !enable_macros =>
        raise ParseError("Macros are disabled")
      IncludeNode(_, _) | ImportNode(_, _) | FromImportNode(_, _) if !enable_multi_template =>
        raise ParseError("Multi-template features are disabled")
      Break | Continue if !enable_loop_controls =>
        raise ParseError("Loop controls are disabled")
      IfBlock(_, then_body, else_body) => {
        validate_node_features(
          then_body, enable_macros, enable_multi_template, enable_loop_controls,
        )
        validate_node_features(
          else_body, enable_macros, enable_multi_template, enable_loop_controls,
        )
      }
      ForBlock(_, _, body, else_body) => {
        validate_node_features(
          body, enable_macros, enable_multi_template, enable_loop_controls,
        )
        validate_node_features(
          else_body, enable_macros, enable_multi_template, enable_loop_controls,
        )
      }
      BlockNode(_, body)
      | WithBlock(_, body)
      | MacroDef(_, _, body)
      | CallBlock(_, body) =>
        validate_node_features(
          body, enable_macros, enable_multi_template, enable_loop_controls,
        )
      _ => ()
    }
  }
}

///|
fn validate_parse_depth(
  tokens : Array[Token],
  max_depth : Int,
) -> Unit raise JinjaError {
  if max_depth <= 0 {
    raise ParseError("Parser nesting limit must be positive")
  }
  let mut delimiter_depth = 0
  let mut block_depth = 0
  let mut unary_depth = 0
  for index, token in tokens {
    match token {
      Delimiter('(') | Delimiter('[') | Delimiter('{') => {
        delimiter_depth += 1
        unary_depth = 0
      }
      Delimiter(')') | Delimiter(']') | Delimiter('}') => {
        delimiter_depth -= 1
        unary_depth = 0
      }
      Operator("not") | Operator("+") | Operator("-") => unary_depth += 1
      BlockStart(_) =>
        match peek(tokens, index + 1) {
          Identifier("if")
          | Identifier("for")
          | Identifier("with")
          | Identifier("block")
          | Identifier("macro")
          | Identifier("call") => block_depth += 1
          Identifier("endif")
          | Identifier("endfor")
          | Identifier("endwith")
          | Identifier("endblock")
          | Identifier("endmacro")
          | Identifier("endcall") => block_depth -= 1
          _ => ()
        }
      _ => unary_depth = 0
    }
    if delimiter_depth > max_depth ||
      block_depth > max_depth ||
      unary_depth > max_depth {
      raise ParseError("Template parser nesting limit exceeded")
    }
  }
}

///|
fn parse_template(
  tokens : Array[Token],
  i : Ref[Int],
  len : Int,
) -> Template raise JinjaError {
  let mut parent : String? = None
  let parsed = parse_expr_list(tokens, i, len)
  let prefix : Array[Expr] = []
  let body : Array[Expr] = []
  let mut saw_extends = false
  for node in parsed {
    match node {
      ExtendsNode(name) =>
        match parent {
          Some(_) => raise ParseError("A template can only extend one parent")
          None => {
            parent = Some(name)
            saw_extends = true
          }
        }
      _ => {
        if !saw_extends {
          prefix.push(node)
        }
        body.push(node)
      }
    }
  }
  if parent is None {
    prefix.clear()
  }
  // Template 是 struct;用记录字面量的“具名”写法,避免字段顺序陷阱
  let tpl : Template = { parent, prefix, body }
  return tpl
}

// 只返回 extends 文件名

///|
fn parse_extends_header(
  tokens : Array[Token],
  i : Ref[Int],
  _len : Int,
) -> String raise JinjaError {
  // 消费 BlockStart
  match tokens[i.val] {
    BlockStart(_) => i.val += 1
    _ => raise ParseError("Expected '{%' before extends")
  }

  // "extends"
  match tokens[i.val] {
    Identifier(id) => {
      if id != "extends" {
        ignore(parse_fail(i, tokens, _len, "Expected 'extends'"))
      }
      i.val += 1
    }
    _ => ignore(parse_fail(i, tokens, _len, "Expected 'extends'"))
  }

  // 字面量文件名
  let filename = match tokens[i.val] {
    StringLiteral(s) => {
      i.val += 1
      s
    }
    _ => raise ParseError("Expected file name after 'extends'")
  }

  // 结束 "%}"(strip/非 strip 都接受)
  match tokens[i.val] {
    BlockEnd(_) => i.val += 1
    _ => raise ParseError("Expected '%}' after extends")
  }
  filename
}

// ==================== Core Helpers ====================

///|
fn peek(tokens : Array[Token], i : Int) -> Token {
  if i < tokens.length() {
    tokens[i]
  } else {
    EOF
  }
}

///|
fn advance(i : Ref[Int]) -> Unit {
  i.val += 1
}

///|
fn is_delim(tok : Token, ch : Char) -> Bool {
  match tok {
    Delimiter(c) => c == ch
    _ => false
  }
}

///|
fn expect_delim(
  tokens : Array[Token],
  i : Ref[Int],
  ch : Char,
  what : String,
) -> Unit raise JinjaError {
  if i.val >= tokens.length() || !is_delim(tokens[i.val], ch) {
    raise ParseError(what)
  }
  i.val += 1
}

// ==================== Value Conversion ====================

///|
fn str_to_int(s : String) -> Int {
  let chars = s.to_array()
  let mut result = 0
  let mut sign = 1
  let mut start = 0
  if chars.length() > 0 && chars[0] == '-' {
    sign = -1
    start = 1
  }
  for i in start.. Value {
  if lit == "true" {
    return BoolValue(true)
  }
  if lit == "false" {
    return BoolValue(false)
  }
  if lit == "null" {
    return Null
  }
  let chars = lit.to_array()
  let mut all_digits = true
  let mut decimal_points = 0
  for c in chars {
    if c == '.' {
      decimal_points += 1
    } else if !('0' <= c && c <= '9') {
      all_digits = false
      break
    }
  }
  if all_digits && decimal_points == 0 {
    return IntValue(str_to_int(lit))
  }
  if all_digits && decimal_points == 1 {
    let parts = lit.split(".").to_array()
    if parts.length() == 2 {
      let whole = str_to_int(parts[0].to_owned()).to_double()
      let fraction_text = parts[1].to_owned()
      let mut divisor = 1.0
      for _ in fraction_text {
        divisor *= 10.0
      }
      return DoubleValue(
        whole + str_to_int(fraction_text).to_double() / divisor,
      )
    }
  }
  return StrValue(lit)
}

// ==================== Expression Parsers ====================

///|
fn parse_expr(
  tokens : Array[Token],
  i : Ref[Int],
  len : Int,
) -> Expr raise JinjaError {
  let value = parse_binary_expr(tokens, i, len, 1)
  match peek(tokens, i.val) {
    Identifier("if") => {
      i.val += 1
      let condition = parse_binary_expr(tokens, i, len, 1)
      let fallback = match peek(tokens, i.val) {
        Identifier("else") => {
          i.val += 1
          Some(parse_expr(tokens, i, len))
        }
        _ => None
      }
      Conditional(value, condition, fallback)
    }
    _ => value
  }
}

///|
fn parse_binary_expr(
  tokens : Array[Token],
  i : Ref[Int],
  len : Int,
  min_prec : Int,
) -> Expr raise JinjaError {
  let mut left = parse_unary_or_primary(tokens, i, len)
  while true {
    let tok = peek(tokens, i.val)
    match tok {
      Identifier("is") => {
        if 50 < min_prec {
          break
        }
        i.val += 1
        let negated = match peek(tokens, i.val) {
          Operator("not") => {
            i.val += 1
            true
          }
          _ => false
        }
        let name = match peek(tokens, i.val) {
          Identifier(name) => {
            i.val += 1
            name
          }
          _ => raise ParseError("Expected test name after 'is'")
        }
        let (args, kwargs) = if is_delim(peek(tokens, i.val), '(') {
          parse_call_args(tokens, i, len)
        } else {
          ([], Map([]))
        }
        left = TestCall(left, name, negated, args, kwargs)
      }
      Identifier("in") => {
        if 50 < min_prec {
          break
        }
        i.val += 1
        let right = parse_binary_expr(tokens, i, len, 51)
        left = Binary(left, BinOp::In, right)
      }
      Operator("not") =>
        match peek(tokens, i.val + 1) {
          Identifier("in") => {
            if 50 < min_prec {
              break
            }
            i.val += 2
            let right = parse_binary_expr(tokens, i, len, 51)
            left = Binary(left, BinOp::NotIn, right)
          }
          _ => break
        }
      Operator(op_str) => {
        let maybe = op_to_binop(op_str)
        match maybe {
          None => break
          Some(binop) => {
            let prec = precedence(binop)
            if prec < min_prec {
              break
            }
            advance(i)
            let right = parse_binary_expr(tokens, i, len, prec + 1)
            left = Expr::Binary(left, binop, right)
          }
        }
      }
      _ => break
    }
  }
  return left
}

///|
fn parse_unary_or_primary(
  tokens : Array[Token],
  i : Ref[Int],
  len : Int,
) -> Expr raise JinjaError {
  let base = match peek(tokens, i.val) {
    Operator("not") => {
      i.val += 1
      Expr::Unary(UnOp::Not, parse_unary_or_primary(tokens, i, len))
    }
    Operator("-") => {
      i.val += 1
      Expr::Unary(UnOp::Neg, parse_unary_or_primary(tokens, i, len))
    }
    Operator("+") => {
      i.val += 1
      Expr::Unary(UnOp::Pos, parse_unary_or_primary(tokens, i, len))
    }
    _ => parse_primary(tokens, i, len)
  }
  parse_postfix(tokens, i, len, base)
}

///|
fn parse_call_args(
  tokens : Array[Token],
  i : Ref[Int],
  len : Int,
) -> (Array[Expr], Map[String, Expr]) raise JinjaError {
  expect_delim(tokens, i, '(', "Expected '(' after filter name")
  let args : Array[Expr] = []
  let kwargs : Map[String, Expr] = Map([])
  let mut saw_keyword = false
  // 空实参 ()
  if is_delim(peek(tokens, i.val), ')') {
    i.val += 1
    return (args, kwargs)
  }
  while true {
    match (peek(tokens, i.val), peek(tokens, i.val + 1)) {
      (Identifier(name), Operator("=")) => {
        if kwargs.contains(name) {
          raise ParseError("Duplicate keyword argument: " + name)
        }
        i.val += 2
        kwargs[name] = parse_expr(tokens, i, len)
        saw_keyword = true
      }
      _ => {
        if saw_keyword {
          raise ParseError(
            "Positional arguments cannot follow keyword arguments",
          )
        }
        args.push(parse_expr(tokens, i, len))
      }
    }
    if is_delim(peek(tokens, i.val), ',') {
      i.val += 1
      if is_delim(peek(tokens, i.val), ')') {
        break
      }
      continue
    }
    break
  }
  expect_delim(tokens, i, ')', "Expected ')' to close argument list")
  (args, kwargs)
}

///|
fn parse_postfix(
  tokens : Array[Token],
  i : Ref[Int],
  len : Int,
  base0 : Expr,
) -> Expr raise JinjaError {
  let mut base = base0
  let filters : Array[Call] = []
  while i.val < len {
    match tokens[i.val] {
      Delimiter('[') => {
        i.val += 1
        let index = parse_expr(tokens, i, len)
        expect_delim(tokens, i, ']', "Expected ']' to close index")
        base = Expr::Index(base, index)
      }
      Operator(op) => {
        if op != "|" {
          break
        } // ← 只在分支体里判断
        i.val += 1 // 吃掉 '|'
        let fname = match tokens[i.val] {
          Identifier(s) => {
            i.val += 1
            s
          }
          _ => raise ParseError("Expected filter name after '|'")
        }
        let (args, kwargs) = if is_delim(tokens[i.val], '(') {
          parse_call_args(tokens, i, len)
        } else {
          ([], Map([]))
        }
        let call : Call = { name: fname, args, kwargs }
        filters.push(call)
      }
      _ => break
    }
  }
  if filters.length() > 0 {
    Expr::FilterChain(base, filters)
  } else {
    base
  }
}

///|
fn parse_primary(
  tokens : Array[Token],
  i : Ref[Int],
  len : Int,
) -> Expr raise JinjaError {
  let tok = peek(tokens, i.val)
  match tok {
    Text(s) => {
      let strip_left = if i.val > 0 {
        match tokens[i.val - 1] {
          VarEnd(true) | BlockEnd(true) => true // ← 左边看 End(true)
          _ => false
        }
      } else {
        false
      }
      let strip_right = if i.val + 1 < len {
        match tokens[i.val + 1] {
          VarStart(true) | BlockStart(true) => true // ← 右边看 Start(true)
          _ => false
        }
      } else {
        false
      }
      i.val += 1
      Expr::TextNode(s, strip_left, strip_right)
    }
    Raw(s) => {
      i.val += 1
      Expr::RawText(s)
    }
    Identifier(first) => {
      i.val += 1
      let parts : Array[String] = Array::new()
      parts.push(first)
      while i.val + 1 < len {
        if is_delim(tokens[i.val], '.') {
          match tokens[i.val + 1] {
            Identifier(seg) => {
              i.val += 2
              parts.push(seg)
              continue
            }
            _ => raise ParseError("Expected identifier after '.'")
          }
        }
        break
      }
      if is_delim(peek(tokens, i.val), '(') {
        let (args, kwargs) = parse_call_args(tokens, i, len)
        return Expr::FunctionCall({ name: parts.join("."), args, kwargs })
      }
      let a : VarPath = { parts, }
      Expr::Var(a)
    }
    Literal(raw) => {
      i.val += 1
      let v = to_value_from_token_lit(raw)
      Expr::Literal(v)
    }
    StringLiteral(value) => {
      i.val += 1
      Expr::Literal(StrValue(value))
    }
    Delimiter('[') => {
      i.val += 1
      let values : Array[Expr] = []
      while !is_delim(peek(tokens, i.val), ']') {
        values.push(parse_expr(tokens, i, len))
        if is_delim(peek(tokens, i.val), ',') {
          i.val += 1
          if is_delim(peek(tokens, i.val), ']') {
            break
          }
        } else {
          break
        }
      }
      expect_delim(tokens, i, ']', "Expected ']' to close list literal")
      Expr::ListLiteral(values)
    }
    Delimiter('{') => {
      i.val += 1
      let entries : Array[(String, Expr)] = []
      while !is_delim(peek(tokens, i.val), '}') {
        let key = match peek(tokens, i.val) {
          StringLiteral(key) | Identifier(key) => {
            i.val += 1
            key
          }
          _ => raise ParseError("Expected string or identifier map key")
        }
        expect_delim(tokens, i, ':', "Expected ':' after map key")
        entries.push((key, parse_expr(tokens, i, len)))
        if is_delim(peek(tokens, i.val), ',') {
          i.val += 1
          if is_delim(peek(tokens, i.val), '}') {
            break
          }
        } else {
          break
        }
      }
      expect_delim(tokens, i, '}', "Expected '}' to close map literal")
      Expr::MapLiteral(entries)
    }
    Delimiter('(') => {
      i.val += 1
      let e = parse_expr(tokens, i, len)
      expect_delim(tokens, i, ')', "Expected ')'")
      e
    }
    _ => raise ParseError("Unsupported token in primary")
  }
}

// ==================== Expression Lists ====================

///|
//fn debug_token_at(tokens : Array[Token], len : Int, idx : Int) -> String {
//  if idx < 0 || idx >= len {
//    ""
//  } else {
//    token_to_string(tokens[idx])
//  }
//}

///|
fn parse_expr_list(
  tokens : Array[Token],
  i : Ref[Int],
  len : Int,
) -> Array[Expr] raise JinjaError {
  let out : Array[Expr] = Array::new()
  while i.val < len {
    let cur = tokens[i.val]
    match cur {
      EOF => return out
      VarStart(_) => {
        i.val += 1
        let e = parse_expr(tokens, i, len)
        if i.val >= len || !is_var_end(tokens[i.val]) {
          raise ParseError("Expected '}}' to close variable tag")
        }
        i.val += 1
        out.push(e)
      }
      BlockStart(_) => {
        // lookahead:这些交给外层块解析器处理
        if i.val + 1 < len {
          match tokens[i.val + 1] {
            Identifier(id) =>
              if id == "endif" ||
                id == "endfor" ||
                id == "endwith" ||
                id == "endblock" ||
                id == "endmacro" ||
                id == "endcall" ||
                id == "else" ||
                id == "elif" {
                return out
              }
            _ => ()
          }
        }
        if i.val + 1 >= len {
          raise ParseError("Unexpected EOF inside block")
        }
        match tokens[i.val + 1] {
          Identifier(id) =>
            if id == "if" {
              let e = parse_if_block(tokens, i, len)
              out.push(e)
            } else if id == "for" {
              let e = parse_for_block(tokens, i, len)
              out.push(e)
            } else if id == "with" {
              let e = parse_with_block(tokens, i, len)
              out.push(e)
            } else if id == "include" {
              let e = parse_include(tokens, i, len)
              out.push(e)
            } else if id == "block" {
              let e = parse_block(tokens, i, len)
              out.push(e)
            } else if id == "set" {
              let e = parse_set_block(tokens, i, len)
              out.push(e)
            } else if id == "macro" {
              out.push(parse_macro_block(tokens, i, len))
            } else if id == "call" {
              out.push(parse_call_block(tokens, i, len))
            } else if id == "import" {
              out.push(parse_import(tokens, i, len))
            } else if id == "from" {
              out.push(parse_from_import(tokens, i, len))
            } else if id == "extends" {
              out.push(ExtendsNode(parse_extends_header(tokens, i, len)))
            } else if id == "break" {
              // 期望:BlockStart(_), Identifier("break"), BlockEnd(_)
              i.val += 1 // {%
              i.val += 1 // break
              if i.val >= len || !is_block_end(tokens[i.val]) {
                raise ParseError("Expected '%}' after break")
              }
              i.val += 1
              out.push(Expr::Break)
            } else if id == "continue" {
              i.val += 1 // {%
              i.val += 1 // continue
              if i.val >= len || !is_block_end(tokens[i.val]) {
                raise ParseError("Expected '%}' after continue")
              }
              i.val += 1
              out.push(Expr::Continue)
            } else {
              raise ParseError("Unknown block tag")
            }
          _ => raise ParseError("Expected block identifier after '{%'")
        }
      }
      _ => {
        let e = parse_primary(tokens, i, len)
        out.push(e)
      }
    }
  }
  out
}

// ==================== Block Parsers ====================

///|
fn parse_if_block(
  tokens : Array[Token],
  i : Ref[Int],
  len : Int,
) -> Expr raise JinjaError {
  // 进入 "{% if"
  match tokens[i.val] {
    BlockStart(_) => i.val += 1
    _ => ignore(parse_fail(i, tokens, len, "Expected '{%' before if"))
  }
  match tokens[i.val] {
    Identifier(id) => {
      if id != "if" {
        ignore(parse_fail(i, tokens, len, "Expected 'if'"))
      }
      i.val += 1
    }
    _ => ignore(parse_fail(i, tokens, len, "Expected 'if'"))
  }
  let cond = parse_expr(tokens, i, len)
  if i.val >= len || !is_block_end(tokens[i.val]) {
    ignore(parse_fail(i, tokens, len, "Expected '%}' after if condition"))
  }
  i.val += 1
  let then_body = parse_expr_list(tokens, i, len)
  let else_body = parse_if_remainder(tokens, i, len)

  // 结束:endif
  match tokens[i.val] {
    BlockStart(_) => i.val += 1
    _ => ignore(parse_fail(i, tokens, len, "Expected '{%' before endif"))
  }
  match tokens[i.val] {
    Identifier(id) => {
      if id != "endif" {
        ignore(parse_fail(i, tokens, len, "Expected 'endif'"))
      }
      i.val += 1
    }
    _ => ignore(parse_fail(i, tokens, len, "Expected 'endif'"))
  }
  if i.val >= len || !is_block_end(tokens[i.val]) {
    ignore(parse_fail(i, tokens, len, "Expected '%}' after endif"))
  }
  i.val += 1
  Expr::IfBlock(cond, then_body, else_body)
}

///|
fn parse_if_remainder(
  tokens : Array[Token],
  i : Ref[Int],
  len : Int,
) -> Array[Expr] raise JinjaError {
  if i.val + 1 >= len || !is_block_start(tokens[i.val]) {
    return []
  }
  match tokens[i.val + 1] {
    Identifier("else") => {
      i.val += 2
      if i.val >= len || !is_block_end(tokens[i.val]) {
        ignore(parse_fail(i, tokens, len, "Expected '%}' after else"))
      }
      i.val += 1
      parse_expr_list(tokens, i, len)
    }
    Identifier("elif") => {
      i.val += 2
      let cond = parse_expr(tokens, i, len)
      if i.val >= len || !is_block_end(tokens[i.val]) {
        ignore(parse_fail(i, tokens, len, "Expected '%}' after elif condition"))
      }
      i.val += 1
      let body = parse_expr_list(tokens, i, len)
      let remainder = parse_if_remainder(tokens, i, len)
      [Expr::IfBlock(cond, body, remainder)]
    }
    _ => []
  }
}

///|
fn parse_for_block(
  tokens : Array[Token],
  i : Ref[Int],
  len : Int,
) -> Expr raise JinjaError {
  match tokens[i.val] {
    BlockStart(_) => i.val += 1
    _ => ignore(parse_fail(i, tokens, len, "Expected '{%' before for"))
  }
  match tokens[i.val] {
    Identifier(id) => {
      if id != "for" {
        ignore(parse_fail(i, tokens, len, "Expected 'for'"))
      }
      i.val += 1
    }
    _ => ignore(parse_fail(i, tokens, len, "Expected 'for'"))
  }
  let loop_var = match tokens[i.val] {
    Identifier(n) => {
      i.val += 1
      n
    }
    _ => parse_fail(i, tokens, len, "Expected loop variable")
  }
  match tokens[i.val] {
    Identifier(id) => {
      if id != "in" {
        ignore(parse_fail(i, tokens, len, "Expected 'in' in for"))
      }
      i.val += 1
    }
    _ => ignore(parse_fail(i, tokens, len, "Expected 'in' in for"))
  }
  let iterable = parse_expr(tokens, i, len)
  if i.val >= len || !is_block_end(tokens[i.val]) {
    ignore(parse_fail(i, tokens, len, "Expected '%}' after for header"))
  }
  i.val += 1

  let body = parse_expr_list(tokens, i, len)
  let mut else_body : Array[Expr] = []
  if i.val + 1 < len && is_block_start(tokens[i.val]) {
    match tokens[i.val + 1] {
      Identifier("else") => {
        i.val += 2
        if i.val >= len || !is_block_end(tokens[i.val]) {
          ignore(parse_fail(i, tokens, len, "Expected '%}' after else"))
        }
        i.val += 1
        else_body = parse_expr_list(tokens, i, len)
      }
      _ => ()
    }
  }

  // "{% endfor %}"
  match tokens[i.val] {
    BlockStart(_) => i.val += 1
    _ => ignore(parse_fail(i, tokens, len, "Expected '{%' before endfor"))
  }
  match tokens[i.val] {
    Identifier(id) => {
      if id != "endfor" {
        ignore(parse_fail(i, tokens, len, "Expected 'endfor'"))
      }
      i.val += 1
    }
    _ => ignore(parse_fail(i, tokens, len, "Expected 'endfor'"))
  }
  if i.val >= len || !is_block_end(tokens[i.val]) {
    ignore(parse_fail(i, tokens, len, "Expected '%}' after endfor"))
  }
  i.val += 1
  Expr::ForBlock(loop_var, iterable, body, else_body)
}

///|
fn parse_include(
  tokens : Array[Token],
  i : Ref[Int],
  _len : Int,
) -> Expr raise JinjaError {
  match tokens[i.val] {
    BlockStart(_) => i.val += 1
    _ => raise ParseError("Expected '{%' before include")
  }
  match tokens[i.val] {
    Identifier(id) => {
      if id != "include" {
        raise ParseError("Expected 'include'")
      }
      i.val += 1
    }
    _ => raise ParseError("Expected 'include'")
  }
  let filename = match tokens[i.val] {
    StringLiteral(s) => {
      i.val += 1
      s
    }
    _ => raise ParseError("Expected filename in include")
  }
  let mut with_context = true
  match peek(tokens, i.val) {
    Identifier("without") => {
      i.val += 1
      match peek(tokens, i.val) {
        Identifier("context") => i.val += 1
        _ => raise ParseError("Expected 'context' after 'without'")
      }
      with_context = false
    }
    Identifier("with") => {
      i.val += 1
      match peek(tokens, i.val) {
        Identifier("context") => i.val += 1
        _ => raise ParseError("Expected 'context' after 'with'")
      }
    }
    _ => ()
  }
  match tokens[i.val] {
    BlockEnd(_) => i.val += 1
    _ => raise ParseError("Expected '%}' after include")
  }
  Expr::IncludeNode(filename, with_context)
}

///|
fn parse_block(
  tokens : Array[Token],
  i : Ref[Int],
  len : Int,
) -> Expr raise JinjaError {
  match tokens[i.val] {
    BlockStart(_) => i.val += 1
    _ => raise ParseError("Expected '{%' before block")
  }
  match tokens[i.val] {
    Identifier(id) => {
      if id != "block" {
        raise ParseError("Expected 'block'")
      }
      i.val += 1
    }
    _ => raise ParseError("Expected 'block'")
  }
  let name = match tokens[i.val] {
    Identifier(n) => {
      i.val += 1
      n
    }
    _ => raise ParseError("Expected block name")
  }
  if i.val >= len || !is_block_end(tokens[i.val]) {
    raise ParseError("Expected '%}' after block")
  }
  i.val += 1
  let body = parse_expr_list(tokens, i, len)
  match tokens[i.val] {
    BlockStart(_) => i.val += 1
    _ => raise ParseError("Expected '{%' before endblock")
  }
  match tokens[i.val] {
    Identifier(id) => {
      if id != "endblock" {
        raise ParseError("Expected 'endblock'")
      }
      i.val += 1
    }
    _ => raise ParseError("Expected 'endblock'")
  }
  if i.val >= len || !is_block_end(tokens[i.val]) {
    raise ParseError("Expected '%}' after endblock")
  }
  i.val += 1
  Expr::BlockNode(name, body)
}

///|
fn parse_set_block(
  tokens : Array[Token],
  i : Ref[Int],
  len : Int,
) -> Expr raise JinjaError {
  match tokens[i.val] {
    BlockStart(_) => i.val += 1
    _ => raise ParseError("Expected '{%' before set")
  }
  match tokens[i.val] {
    Identifier(id) => {
      if id != "set" {
        raise ParseError("Expected 'set'")
      }
      i.val += 1
    }
    _ => raise ParseError("Expected 'set'")
  }
  let name = match tokens[i.val] {
    Identifier(n) => {
      i.val += 1
      n
    }
    _ => raise ParseError("Expected var name")
  }
  match tokens[i.val] {
    Operator(op) => {
      if op != "=" {
        raise ParseError("Expected '=' after var name")
      }
      i.val += 1
    }
    _ => raise ParseError("Expected '=' after var name")
  }
  let value = parse_expr(tokens, i, len)
  if i.val >= len || !is_block_end(tokens[i.val]) {
    raise ParseError("Expected '%}' after set")
  }
  i.val += 1
  Expr::SetStmt(name, value)
}

///|
fn parse_with_block(
  tokens : Array[Token],
  i : Ref[Int],
  len : Int,
) -> Expr raise JinjaError {
  // "{% with"
  match tokens[i.val] {
    BlockStart(_) => i.val += 1
    _ => raise ParseError("Expected '{%' before with")
  }
  match tokens[i.val] {
    Identifier(id) => {
      if id != "with" {
        raise ParseError("Expected 'with'")
      }
      i.val += 1
    }
    _ => raise ParseError("Expected 'with'")
  }
  let assigns : Map[String, Expr] = Map([])
  while true {
    // name
    let name = match tokens[i.val] {
      Identifier(n) => {
        i.val += 1
        n
      }
      _ => raise ParseError("Expected var name in 'with'")
    }
    // "="
    match tokens[i.val] {
      Operator(op) => {
        if op != "=" {
          raise ParseError("Expected '=' after var name")
        }
        i.val += 1
      }
      _ => raise ParseError("Expected '=' after var name")
    }
    // value expr
    let value = parse_expr(tokens, i, len)
    assigns[name] = value

    // ',' 继续,或 '%}' 结束
    if i.val < len {
      match tokens[i.val] {
        Delimiter(',') => {
          i.val += 1
          continue
        }
        BlockEnd(_) => {
          i.val += 1
          break
        }
        _ => raise ParseError("Expected ',' or '%}' in with")
      }
    } else {
      raise ParseError("Unexpected EOF in with")
    }
  }
  let body = parse_expr_list(tokens, i, len)

  // "{% endwith %}"
  match tokens[i.val] {
    BlockStart(_) => i.val += 1
    _ => raise ParseError("Expected '{%' before endwith")
  }
  match tokens[i.val] {
    Identifier(id) => {
      if id != "endwith" {
        raise ParseError("Expected 'endwith'")
      }
      i.val += 1
    }
    _ => raise ParseError("Expected 'endwith'")
  }
  if i.val >= len || !is_block_end(tokens[i.val]) {
    raise ParseError("Expected '%}' after endwith")
  }
  i.val += 1
  Expr::WithBlock(assigns, body)
}

///|
fn expect_block_end(
  tokens : Array[Token],
  i : Ref[Int],
  message : String,
) -> Unit raise JinjaError {
  match peek(tokens, i.val) {
    BlockEnd(_) => i.val += 1
    _ => raise ParseError(message)
  }
}

///|
fn parse_end_tag(
  tokens : Array[Token],
  i : Ref[Int],
  name : String,
) -> Unit raise JinjaError {
  match peek(tokens, i.val) {
    BlockStart(_) => i.val += 1
    _ => raise ParseError("Expected '{%' before " + name)
  }
  match peek(tokens, i.val) {
    Identifier(actual) if actual == name => i.val += 1
    _ => raise ParseError("Expected '" + name + "'")
  }
  expect_block_end(tokens, i, "Expected '%}' after " + name)
}

///|
fn parse_macro_block(
  tokens : Array[Token],
  i : Ref[Int],
  len : Int,
) -> Expr raise JinjaError {
  i.val += 2 // BlockStart, macro
  let name = match peek(tokens, i.val) {
    Identifier(name) => {
      i.val += 1
      name
    }
    _ => raise ParseError("Expected macro name")
  }
  expect_delim(tokens, i, '(', "Expected '(' after macro name")
  let params : Array[MacroParam] = []
  let param_names : Map[String, Bool] = Map([])
  let mut saw_default = false
  if !is_delim(peek(tokens, i.val), ')') {
    while true {
      let param_name = match peek(tokens, i.val) {
        Identifier(name) => {
          i.val += 1
          name
        }
        _ => raise ParseError("Expected macro parameter")
      }
      if param_names.contains(param_name) {
        raise ParseError("Duplicate macro parameter: " + param_name)
      }
      param_names[param_name] = true
      let default = match peek(tokens, i.val) {
        Operator("=") => {
          i.val += 1
          saw_default = true
          Some(parse_expr(tokens, i, len))
        }
        _ => {
          if saw_default {
            raise ParseError(
              "Required macro parameters cannot follow default parameters",
            )
          }
          None
        }
      }
      params.push({ name: param_name, default })
      match peek(tokens, i.val) {
        Delimiter(',') => i.val += 1
        Delimiter(')') => break
        _ => raise ParseError("Expected ',' or ')' in macro parameters")
      }
    }
  }
  expect_delim(tokens, i, ')', "Expected ')' after macro parameters")
  expect_block_end(tokens, i, "Expected '%}' after macro declaration")
  let body = parse_expr_list(tokens, i, len)
  parse_end_tag(tokens, i, "endmacro")
  MacroDef(name, params, body)
}

///|
fn parse_call_block(
  tokens : Array[Token],
  i : Ref[Int],
  len : Int,
) -> Expr raise JinjaError {
  i.val += 2 // BlockStart, call
  let call = match parse_expr(tokens, i, len) {
    FunctionCall(call) => call
    _ => raise ParseError("call expects a macro invocation")
  }
  expect_block_end(tokens, i, "Expected '%}' after call")
  let body = parse_expr_list(tokens, i, len)
  parse_end_tag(tokens, i, "endcall")
  CallBlock(call, body)
}

///|
fn parse_import(
  tokens : Array[Token],
  i : Ref[Int],
  _len : Int,
) -> Expr raise JinjaError {
  i.val += 2 // BlockStart, import
  let filename = match peek(tokens, i.val) {
    StringLiteral(filename) => {
      i.val += 1
      filename
    }
    _ => raise ParseError("Expected template name after import")
  }
  match peek(tokens, i.val) {
    Identifier("as") => i.val += 1
    _ => raise ParseError("Expected 'as' in import")
  }
  let import_name = match peek(tokens, i.val) {
    Identifier(import_name) => {
      i.val += 1
      import_name
    }
    _ => raise ParseError("Expected namespace after import as")
  }
  expect_block_end(tokens, i, "Expected '%}' after import")
  ImportNode(filename, import_name)
}

///|
fn parse_from_import(
  tokens : Array[Token],
  i : Ref[Int],
  _len : Int,
) -> Expr raise JinjaError {
  i.val += 2 // BlockStart, from
  let filename = match peek(tokens, i.val) {
    StringLiteral(filename) => {
      i.val += 1
      filename
    }
    _ => raise ParseError("Expected template name after from")
  }
  match peek(tokens, i.val) {
    Identifier("import") => i.val += 1
    _ => raise ParseError("Expected 'import' after template name")
  }
  let names : Array[String] = []
  while true {
    match peek(tokens, i.val) {
      Identifier(name) => {
        names.push(name)
        i.val += 1
      }
      _ => raise ParseError("Expected macro name in from import")
    }
    match peek(tokens, i.val) {
      Delimiter(',') => i.val += 1
      BlockEnd(_) => break
      _ => raise ParseError("Expected ',' or '%}' in from import")
    }
  }
  expect_block_end(tokens, i, "Expected '%}' after from import")
  FromImportNode(filename, names)
}

///|
fn validate_control_flow(
  nodes : Array[Expr],
  loop_depth : Int,
) -> Unit raise JinjaError {
  for node in nodes {
    match node {
      Break | Continue if loop_depth == 0 =>
        raise ParseError("break and continue are only valid inside a for loop")
      IfBlock(_, then_body, else_body) => {
        validate_control_flow(then_body, loop_depth)
        validate_control_flow(else_body, loop_depth)
      }
      ForBlock(_, _, body, else_body) => {
        validate_control_flow(body, loop_depth + 1)
        validate_control_flow(else_body, loop_depth)
      }
      BlockNode(_, body) | WithBlock(_, body) =>
        validate_control_flow(body, loop_depth)
      MacroDef(_, _, body) | CallBlock(_, body) =>
        validate_control_flow(body, 0)
      ExtendsNode(_) =>
        raise ParseError("extends is only valid at template top level")
      _ => ()
    }
  }
}

///|
fn validate_unique_blocks(
  nodes : Array[Expr],
  names : Map[String, Bool],
) -> Unit raise JinjaError {
  for node in nodes {
    match node {
      BlockNode(name, body) => {
        if names.contains(name) {
          raise ParseError("Duplicate block: " + name)
        }
        names[name] = true
        validate_unique_blocks(body, names)
      }
      IfBlock(_, then_body, else_body) => {
        validate_unique_blocks(then_body, names)
        validate_unique_blocks(else_body, names)
      }
      ForBlock(_, _, body, else_body) => {
        validate_unique_blocks(body, names)
        validate_unique_blocks(else_body, names)
      }
      WithBlock(_, body) | MacroDef(_, _, body) | CallBlock(_, body) =>
        validate_unique_blocks(body, names)
      _ => ()
    }
  }
}