///|
priv enum PatternPiece {
  Element(Node)
  Indent(String, Int32, Int32)
}

///|
fn Cursor::maybe_pattern(self : Cursor) -> Node? raise ParseFailure {
  ignore(self.peek_blank_inline())
  if self.is_value_start() {
    self.skip_to_peek()
    return Some(self.pattern(false))
  }
  ignore(self.peek_blank_block())
  if self.is_value_continuation() {
    self.skip_to_peek()
    return Some(self.pattern(true))
  }
  None
}

///|
fn Cursor::text_element(self : Cursor) -> Node {
  let start = self.index
  let buf = StringBuilder()
  while true {
    match self.current() {
      None | Some('{') | Some('}') | Some('\n') => break
      Some(c) => {
        buf.write_char(c)
        ignore(self.next())
      }
    }
  }
  Node::with_span(TextElement(buf.to_string()), start, self.index)
}

///|
fn trim_pattern_end(value : String) -> String {
  let c = Cursor::new(value)
  let mut end = c.chars.length()
  while end > 0 &&
        (
          c.chars[end - 1] == ' ' ||
          c.chars[end - 1] == '\n' ||
          c.chars[end - 1] == '\r'
        ) {
    end -= 1
  }
  c.slice(0, end)
}

///|
fn dedent(pieces : Array[PatternPiece], common : Int32) -> Array[Node] {
  let trimmed : Array[Node] = []
  for piece in pieces {
    let element = match piece {
      Element(node) => node
      Indent(value, start, end) => {
        let c = Cursor::new(value)
        let length = c.chars.length() - common
        if length <= 0 {
          continue
        }
        Node::with_span(TextElement(c.slice(0, length)), start, end)
      }
    }
    match element.kind {
      TextElement(value) => {
        if !trimmed.is_empty() {
          let last = trimmed[trimmed.length() - 1]
          match last.kind {
            TextElement(previous) => {
              trimmed[trimmed.length() - 1] = Node::with_span(
                TextElement(previous + value),
                last.span.start,
                element.span.end,
              )
              continue
            }
            _ => ()
          }
        }
        trimmed.push(element)
      }
      _ => trimmed.push(element)
    }
  }
  if !trimmed.is_empty() {
    let i = trimmed.length() - 1
    let last = trimmed[i]
    match last.kind {
      TextElement(value) => {
        let value = trim_pattern_end(value)
        if value.is_empty() {
          ignore(trimmed.pop())
        } else {
          trimmed[i] = { kind: TextElement(value), span: last.span, }
        }
      }
      _ => ()
    }
  }
  trimmed
}

///|
fn Cursor::pattern(self : Cursor, is_block : Bool) -> Node raise ParseFailure {
  let start = self.index
  let pieces : Array[PatternPiece] = []
  let mut common = 2147483647
  if is_block {
    let blank_start = self.index
    let indent = self.skip_blank_inline()
    pieces.push(Indent(indent, blank_start, self.index))
    common = indent.length()
  }
  while self.current() != None {
    if self.current() == Some('\n') {
      let blank_start = self.index
      let blank_lines = self.peek_blank_block()
      if self.is_value_continuation() {
        self.skip_to_peek()
        let indent = self.skip_blank_inline()
        if indent.length() < common {
          common = indent.length()
        }
        pieces.push(Indent(blank_lines + indent, blank_start, self.index))
        continue
      }
      self.reset_peek()
      break
    }
    if self.current() == Some('}') {
      raise ParseFailure("E0027", [])
    }
    let element = if self.current() == Some('{') {
      self.placeable()
    } else {
      self.text_element()
    }
    pieces.push(Element(element))
  }
  Node::with_span(Pattern(dedent(pieces, common)), start, self.index)
}