///|
pub(all) enum Argument {
  Flag(String)
  Literal(String)
  Strings(Array[String])
  Quantity(Int)
} derive(Eq, Debug)

///|
pub(all) enum Test {
  Call(String, Array[Argument], Span)
  Not(Test, Span)
  AnyOf(Array[Test], Span)
  AllOf(Array[Test], Span)
} derive(Eq, Debug)

///|
pub(all) struct Conditional {
  condition : Test
  body : Array[Statement]
} derive(Eq, Debug)

///|
pub(all) enum Statement {
  Command(String, Array[Argument], Span)
  Branch(Array[Conditional], Array[Statement], Span)
} derive(Eq, Debug)

///|
pub(all) struct Script {
  statements : Array[Statement]
} derive(Eq, Debug)

///|
priv struct Parser {
  tokens : Array[Token]
  mut pos : Int
  limits : Limits
}

///|
fn Parser::current(self : Parser) -> Token {
  self.tokens[self.pos]
}

///|
fn Parser::is_symbol(self : Parser, c : Char) -> Bool {
  self.current().kind == Symbol(c)
}

///|
fn Parser::expect(self : Parser, c : Char) -> Unit raise SieveError {
  if !self.is_symbol(c) {
    fail("parse.expected", "expected punctuation \{c}", self.current().span)
  }
  self.pos += 1
}

///|
fn Parser::arguments(self : Parser) -> Array[Argument] raise SieveError {
  let result : Array[Argument] = []
  while true {
    match self.current().kind {
      Tag(s) => {
        result.push(Flag(s))
        self.pos += 1
      }
      Text(s) => {
        result.push(Literal(s))
        self.pos += 1
      }
      Number(n) => {
        result.push(Quantity(n))
        self.pos += 1
      }
      Symbol('[') => {
        self.pos += 1
        let strings : Array[String] = []
        while true {
          match self.current().kind {
            Text(s) => {
              strings.push(s)
              self.pos += 1
            }
            _ =>
              fail(
                "parse.string_list",
                "expected a nonempty string list",
                self.current().span,
              )
          }
          if self.is_symbol(']') {
            self.pos += 1
            break
          }
          self.expect(',')
        }
        result.push(Strings(strings))
      }
      _ => break
    }
  }
  result
}

///|
fn Parser::test_expr(self : Parser, depth : Int) -> Test raise SieveError {
  if depth > self.limits.depth {
    fail("limit.depth", "condition nesting too deep", self.current().span)
  }
  let token = self.current()
  let name = match token.kind {
    Word(s) => s
    _ => {
      fail("parse.condition", "expected condition name", token.span)
      ""
    }
  }
  self.pos += 1
  match name {
    "not" => Not(self.test_expr(depth + 1), token.span)
    "anyof" | "allof" => {
      self.expect('(')
      let tests : Array[Test] = []
      while true {
        tests.push(self.test_expr(depth + 1))
        if self.is_symbol(')') {
          self.pos += 1
          break
        }
        self.expect(',')
      }
      if name == "anyof" {
        AnyOf(tests, token.span)
      } else {
        AllOf(tests, token.span)
      }
    }
    _ => Call(name, self.arguments(), token.span)
  }
}

///|
fn Parser::block(
  self : Parser,
  depth : Int,
) -> Array[Statement] raise SieveError {
  self.expect('{')
  let body = self.statements(depth + 1, true)
  self.expect('}')
  body
}

///|
fn Parser::statements(
  self : Parser,
  depth : Int,
  nested : Bool,
) -> Array[Statement] raise SieveError {
  if depth > self.limits.depth {
    fail("limit.depth", "statement nesting too deep", self.current().span)
  }
  let out : Array[Statement] = []
  while self.current().kind != End {
    if nested && self.is_symbol('}') {
      break
    }
    let token = self.current()
    let name = match token.kind {
      Word(s) => s
      _ => {
        fail("parse.command", "expected command or if", token.span)
        ""
      }
    }
    self.pos += 1
    if name == "if" {
      let choices : Array[Conditional] = []
      let condition = self.test_expr(depth + 1)
      choices.push({ condition, body: self.block(depth) })
      while self.current().kind == Word("elsif") {
        self.pos += 1
        let condition = self.test_expr(depth + 1)
        choices.push({ condition, body: self.block(depth) })
      }
      let otherwise = if self.current().kind == Word("else") {
        self.pos += 1
        self.block(depth)
      } else {
        []
      }
      out.push(Branch(choices, otherwise, token.span))
    } else {
      if name == "else" || name == "elsif" {
        fail(
          "parse.orphan_branch",
          "branch must immediately follow if",
          token.span,
        )
      }
      let args = self.arguments()
      self.expect(';')
      out.push(Command(name, args, token.span))
    }
  }
  out
}

///|
pub fn parse(
  source : String,
  limits? : Limits = Limits::default(),
) -> Script raise SieveError {
  let parser : Parser = { tokens: tokenize(source, limits~), pos: 0, limits }
  { statements: parser.statements(0, false) }
}