// Token types used by the lexer

///|
priv enum Token {
  Text(String) // Plain text
  Raw(String) // Content inside a raw/endraw block
  VarStart(Bool) // Start of a variable tag: {{ or {{- (with strip_left)
  VarEnd(Bool) // End of a variable tag: }} or -}}
  BlockStart(Bool) // Start of a control block: {% or {%- (with strip_left)
  BlockEnd(Bool) // End of a control block: %} or -%}
  Identifier(String) // Keywords or variable names
  StringLiteral(String)
  Literal(String) // Numeric, boolean, or null literal
  Operator(String) // Operators: ==, +, -, etc.
  Delimiter(Char) // Symbols: (, ), :, ., ,
  EOF // End of input
}

///|
priv struct TokenStream {
  tokens : Array[Token]
  spans : Array[SourceSpan]
}

///|
priv struct NamedBlockTag {
  next_index : Int
  strip_left : Bool
  strip_right : Bool
}

// Equality comparison for tokens

// Convert a token to its string representation (for debugging)

///|
//fn token_to_string(tok : Token) -> String {
//  match tok {
//    Text(s) => "Text(" + s + ")"
//    VarStart(strip) => "VarStart(" + strip.to_string() + ")"
//    VarEnd(strip) => "VarEnd(" + strip.to_string() + ")"
//    BlockStart(strip) => "BlockStart(" + strip.to_string() + ")"
//    BlockEnd(strip) => "BlockEnd(" + strip.to_string() + ")"
//    CommentStart => "CommentStart"
//    CommentEnd => "CommentEnd"
//    Identifier(s) => "Identifier(" + s + ")"
//    Literal(s) => "Literal(" + s + ")"
//    Operator(s) => "Operator(" + s + ")"          // ← 加上
//    Delimiter(c) => "Delimiter(" + c.to_string() + ")"  // ← 加上
//    EOF => "EOF"
//  }
//}

// Main lexer function: converts source string into a list of tokens

///|
fn tokenize(source : String) -> TokenStream raise JinjaError {
  let tokens : Ref[Array[Token]] = { val: [] }
  let starts : Ref[Array[Int]] = { val: [] }
  let ends : Ref[Array[Int]] = { val: [] }
  let chars = source.to_array()
  let i : Ref[Int] = { val: 0 }
  let len = chars.length()
  if len == 0 {
    return {
      tokens: [EOF],
      spans: [
        { start: 0, end: 0, line: 1, column: 1, end_line: 1, end_column: 1 },
      ],
    }
  }
  while i.val < len {
    let curr = chars[i.val]
    //println("tokenize: index=\{i.val}, char='\{curr}'")

    // === Handle {{ (expression start) ===
    if i.val + 1 < len && curr == '{' && chars[i.val + 1] == '{' {
      let strip_right = i.val + 2 < len && chars[i.val + 2] == '-'
      push_token(
        tokens,
        starts,
        ends,
        VarStart(strip_right),
        i.val,
        i.val + (if strip_right { 3 } else { 2 }),
      )
      //println("Detected VarStart (strip=\{strip_right}) at index \{i.val}")
      i.val += if strip_right { 3 } else { 2 }
      parse_expr_content(chars, i, tokens, starts, ends)
      continue
    }

    // === Handle {% (block start) ===
    if i.val + 1 < len && curr == '{' && chars[i.val + 1] == '%' {
      match match_named_block_tag(chars, i.val, "raw") {
        Some(opening) =>
          match find_named_block_tag(chars, opening.next_index, "endraw") {
            Some((content_end, closing)) => {
              let raw_chars : Array[Char] = []
              for index in opening.next_index.. tokens.val[last] = Text(trim_ws_end(text))
                  _ => ()
                }
              }
              push_token(
                tokens,
                starts,
                ends,
                Raw(raw_text),
                i.val,
                closing.next_index,
              )
              i.val = closing.next_index
              if closing.strip_right {
                while i.val < len && is_tag_ws(chars[i.val]) {
                  i.val += 1
                }
              }
              continue
            }
            None =>
              raise LexerError(
                lexer_message(chars, i.val, "Unterminated raw block"),
              )
          }
        None => ()
      }
      let strip_right = i.val + 2 < len && chars[i.val + 2] == '-'
      push_token(
        tokens,
        starts,
        ends,
        BlockStart(strip_right),
        i.val,
        i.val + (if strip_right { 3 } else { 2 }),
      )
      //println("Detected BlockStart (strip=\{strip_right}) at index \{i.val}")
      i.val += if strip_right { 3 } else { 2 }
      parse_block_content(chars, i, tokens, starts, ends)
      continue
    }

    // === Handle {# (comment start) ===
    if i.val + 1 < len && curr == '{' && chars[i.val + 1] == '#' {
      //println("Detected CommentStart at index \{i.val}")
      let strip_left = i.val + 2 < len && chars[i.val + 2] == '-'
      if strip_left && !tokens.val.is_empty() {
        let last = tokens.val.length() - 1
        match tokens.val[last] {
          Text(text) => tokens.val[last] = Text(trim_ws_end(text))
          _ => ()
        }
      }
      i.val += if strip_left { 3 } else { 2 }
      let strip_right = skip_comment_block(chars, i)
      if strip_right {
        while i.val < len && is_tag_ws(chars[i.val]) {
          i.val += 1
        }
      }
      continue
    }

    // === Fallback: Text block ===
    let start = i.val
    while i.val < len {
      let c = chars[i.val]
      if c == '{' && i.val + 1 < len {
        let peek = chars[i.val + 1]
        if peek == '{' || peek == '%' || peek == '#' {
          break
        }
      }
      i.val += 1
    }
    if start < i.val {
      let text_chars : Array[Char] = []
      for k in start.. Unit {
  tokens.val.push(token)
  starts.val.push(start)
  ends.val.push(end)
}

///|
fn build_source_spans(
  chars : Array[Char],
  starts : Array[Int],
  ends : Array[Int],
) -> Array[SourceSpan] {
  let byte_offsets : Array[Int] = [0]
  let lines : Array[Int] = [1]
  let columns : Array[Int] = [1]
  for char in chars {
    byte_offsets.push(byte_offsets.last().unwrap_or(0) + utf8_char_size(char))
    if char == '\n' {
      lines.push(lines.last().unwrap_or(1) + 1)
      columns.push(1)
    } else {
      lines.push(lines.last().unwrap_or(1))
      columns.push(columns.last().unwrap_or(1) + 1)
    }
  }
  let spans : Array[SourceSpan] = []
  for index in 0.. Int {
  let codepoint = char.to_int()
  if codepoint <= 0x7f {
    1
  } else if codepoint <= 0x7ff {
    2
  } else if codepoint <= 0xffff {
    3
  } else {
    4
  }
}

///|
fn is_tag_ws(char : Char) -> Bool {
  char == ' ' || char == '\t' || char == '\n' || char == '\r'
}

///|
/// Returns the first character after a simple `{% name %}` tag.
fn match_named_block_tag(
  chars : Array[Char],
  start : Int,
  name : String,
) -> NamedBlockTag? {
  if start + 1 >= chars.length() ||
    chars[start] != '{' ||
    chars[start + 1] != '%' {
    return None
  }
  let mut index = start + 2
  let strip_left = index < chars.length() && chars[index] == '-'
  if strip_left {
    index += 1
  }
  while index < chars.length() && is_tag_ws(chars[index]) {
    index += 1
  }
  let name_chars = name.to_array()
  for char in name_chars {
    if index >= chars.length() || chars[index] != char {
      return None
    }
    index += 1
  }
  while index < chars.length() && is_tag_ws(chars[index]) {
    index += 1
  }
  let strip_right = index < chars.length() && chars[index] == '-'
  if strip_right {
    index += 1
  }
  if index + 1 < chars.length() &&
    chars[index] == '%' &&
    chars[index + 1] == '}' {
    Some({ next_index: index + 2, strip_left, strip_right })
  } else {
    None
  }
}

///|
fn find_named_block_tag(
  chars : Array[Char],
  start : Int,
  name : String,
) -> (Int, NamedBlockTag)? {
  let mut index = start
  while index + 1 < chars.length() {
    if chars[index] == '{' && chars[index + 1] == '%' {
      match match_named_block_tag(chars, index, name) {
        Some(tag) => return Some((index, tag))
        None => ()
      }
    }
    index += 1
  }
  None
}

// 通用的 block / expression 内容解析函数

///|
fn parse_expr_content(
  chars : Array[Char],
  i : Ref[Int],
  tokens : Ref[Array[Token]],
  starts : Ref[Array[Int]],
  ends : Ref[Array[Int]],
) -> Unit raise JinjaError {
  parse_tag_content(chars, i, tokens, starts, ends, "}}")
}

///|
fn parse_block_content(
  chars : Array[Char],
  i : Ref[Int],
  tokens : Ref[Array[Token]],
  starts : Ref[Array[Int]],
  ends : Ref[Array[Int]],
) -> Unit raise JinjaError {
  parse_tag_content(chars, i, tokens, starts, ends, "%}")
}

// 跳过注释块 {# ... #}

///|
fn skip_comment_block(
  chars : Array[Char],
  i : Ref[Int],
) -> Bool raise JinjaError {
  while i.val + 1 < chars.length() {
    if chars[i.val] == '#' && chars[i.val + 1] == '}' {
      let strip_right = i.val > 0 && chars[i.val - 1] == '-'
      i.val += 2
      return strip_right
    }
    i.val += 1
  }
  raise LexerError(lexer_message(chars, i.val, "Unterminated comment block"))
}

// 通用 tag 内内容解析函数(支持 expression 和 block)

///|
fn parse_tag_content(
  chars : Array[Char],
  i : Ref[Int],
  tokens : Ref[Array[Token]],
  starts : Ref[Array[Int]],
  ends : Ref[Array[Int]],
  end_marker : String,
) -> Unit raise JinjaError {
  let len = chars.length()
  //println("parse_tag_content start at index \{i.val}, char = \{chars[i.val]}")
  while i.val < len {
    // 跳过空白字符
    while i.val < len &&
          (chars[i.val] == ' ' || chars[i.val] == '\n' || chars[i.val] == '\t') {
      i.val += 1
    }
    if i.val >= len {
      break
    }

    // ==== 新增:优先匹配 "-%}" / "-}}" (当前位置是 '-')====
    if i.val + 2 < len &&
      chars[i.val] == '-' &&
      chars[i.val + 1].to_int() == end_marker[0].to_int() &&
      chars[i.val + 2].to_int() == end_marker[1].to_int() {
      // 这是 strip-left 的结束
      let token_start = i.val
      push_token(
        tokens,
        starts,
        ends,
        if end_marker == "}}" {
          VarEnd(true)
        } else {
          BlockEnd(true)
        },
        token_start,
        token_start + 3,
      )
      i.val += 3 // 消费 "-%}" 或 "-}}"
      return // 结束当前 tag
    }

    // ==== 原有:匹配 "%}" / "}}" (当前位置是 end_marker[0])====
    if i.val + 1 < len &&
      chars[i.val].to_int() == end_marker[0].to_int() &&
      chars[i.val + 1].to_int() == end_marker[1].to_int() {
      // 是否有 strip-left(前一位是 '-')
      let strip_left = i.val > 0 && chars[i.val - 1] == '-'
      if strip_left {
        push_token(
          tokens,
          starts,
          ends,
          if end_marker == "}}" {
            VarEnd(true)
          } else {
            BlockEnd(true)
          },
          i.val,
          i.val + 2,
        )
        i.val += 2
      } else {
        push_token(
          tokens,
          starts,
          ends,
          if end_marker == "}}" {
            VarEnd(false)
          } else {
            BlockEnd(false)
          },
          i.val,
          i.val + 2,
        )
        i.val += 2 // 普通 "%}" / "}}"
      }
      return
    }

    //  正常匹配 token 分支
    let c = chars[i.val]
    match c {
      // 标识符
      'a'..='z' | 'A'..='Z' | '_' => {
        let token_start = i.val
        let (ident, next_i) = parse_identifier(chars, i.val)
        //println("Parsed Identifier: \{ident}")
        if ident == "and" || ident == "or" || ident == "not" {
          push_token(tokens, starts, ends, Operator(ident), token_start, next_i)
        } else if ident == "true" || ident == "false" || ident == "null" {
          push_token(tokens, starts, ends, Literal(ident), token_start, next_i)
        } else {
          push_token(
            tokens,
            starts,
            ends,
            Identifier(ident),
            token_start,
            next_i,
          )
        }
        i.val = next_i
      }

      // 字面量(字符串或数字)
      '0'..='9' | '"' | '\'' => {
        let token_start = i.val
        let (lit, next_i, is_string) = parse_literal(chars, i.val)
        //println("Parsed Literal: \{lit}")
        push_token(
          tokens,
          starts,
          ends,
          if is_string {
            StringLiteral(lit)
          } else {
            Literal(lit)
          },
          token_start,
          next_i,
        )
        i.val = next_i
      }

      // 操作符
      '=' | '!' | '<' | '>' | '+' | '-' | '*' | '/' | '%' | '|' | '~' => {
        let token_start = i.val
        let (op, next_i) = parse_operator(chars, i.val)
        //println("Parsed Operator: \{op}")
        push_token(tokens, starts, ends, Operator(op), token_start, next_i)
        i.val = next_i
      }

      // 符号
      '(' | ')' | '[' | ']' | '{' | '}' | ':' | '.' | ',' => {
        //println("Parsed Delimiter: \{c}")
        push_token(tokens, starts, ends, Delimiter(c), i.val, i.val + 1)
        i.val += 1
      }

      // 其他非法字符
      _ =>
        //println("Unexpected character: \{c}")
        raise LexerError(
          lexer_message(chars, i.val, "Unexpected character in tag"),
        )
    }
    if i.val < len {
      //println("No end marker at index \{i.val}, char = \{chars[i.val]}")
    }
  }
  raise LexerError(lexer_message(chars, i.val, "Unterminated tag content"))
}

///|
fn lexer_message(chars : Array[Char], index : Int, message : String) -> String {
  let mut line = 1
  let mut column = 1
  let stop = if index < chars.length() { index } else { chars.length() }
  for offset in 0.. (String, Int) raise JinjaError {
  let mut j = i
  let len = chars.length()
  if j >= len {
    raise LexerError(
      lexer_message(
        chars, i, "Unexpected end of input while parsing identifier",
      ),
    )
  }
  let start = i
  let s = []
  while j < len &&
        (
          ('a' <= chars[j] && chars[j] <= 'z') ||
          ('A' <= chars[j] && chars[j] <= 'Z') ||
          ('0' <= chars[j] && chars[j] <= '9') ||
          chars[j] == '_'
        ) {
    j += 1
  }
  for k in start.. (String, Int, Bool) raise JinjaError {
  let len = chars.length()
  let mut j = i
  let s : Array[Char] = []
  if j >= len {
    raise LexerError(
      lexer_message(chars, i, "Unexpected end of input while parsing literal"),
    )
  }

  // 字符串字面量
  if chars[j] == '"' || chars[j] == '\'' {
    let quote = chars[j]
    j += 1
    while j < len {
      if chars[j] == '\\' && j + 1 < len {
        let escaped = match chars[j + 1] {
          'n' => '\n'
          'r' => '\r'
          't' => '\t'
          '\\' => '\\'
          '"' => '"'
          '\'' => '\''
          other => other
        }
        s.push(escaped)
        j += 2
      } else if chars[j] == quote {
        return (String::from_array(s), j + 1, true)
      } else {
        s.push(chars[j])
        j += 1
      }
    }
    raise LexerError(lexer_message(chars, j, "Unterminated string literal"))
  }

  while j < len && '0' <= chars[j] && chars[j] <= '9' {
    s.push(chars[j])
    j += 1
  }
  if j + 1 < len &&
    chars[j] == '.' &&
    '0' <= chars[j + 1] &&
    chars[j + 1] <= '9' {
    s.push('.')
    j += 1
    while j < len && '0' <= chars[j] && chars[j] <= '9' {
      s.push(chars[j])
      j += 1
    }
  }
  return (String::from_array(s), j, false)
}

///|
fn parse_operator(
  chars : Array[Char],
  i : Int,
) -> (String, Int) raise JinjaError {
  let len = chars.length()
  if i >= len {
    raise LexerError(
      lexer_message(chars, i, "Unexpected end of input while parsing operator"),
    )
  }

  // 两字符操作符
  if i + 1 < len {
    let two = String::from_array([chars[i], chars[i + 1]])
    if ["==", "!=", ">=", "<=", "//", "**"].contains(two) {
      return (two, i + 2)
    }
  }

  // 单字符操作符
  return (chars[i].to_string(), i + 1)
}