///|
priv struct Parser {
  lexer : Lexer
  indent_stack : Array[Int]
  all_indent_levels : Array[Int]
}

///|
fn Parser::new(lexer : Lexer) -> Parser {
  { lexer, indent_stack: [], all_indent_levels: [0] }
}

///|
fn Parser::read_value(
  self : Parser,
  depth : Int,
) -> Result[Value?, NestedTextError] {
  match self.lexer.peek() {
    None => Ok(None)
    Some(line) =>
      if line.depth < depth {
        Ok(None)
      } else if line.depth > depth {
        if depth == 0 && self.indent_stack.is_empty() {
          Err(
            NestedTextError::at(
              InvalidIndentLevel,
              "top-level content must start in column 1.",
              line.lineno,
              1,
              line.text,
            ),
          )
        } else if !self.indent_stack.is_empty() &&
          !self.indent_stack.contains(line.depth) {
          Err(
            NestedTextError::at(
              InvalidIndentLevel,
              "invalid indentation, partial dedent.",
              line.lineno,
              1,
              line.text,
            ),
          )
        } else {
          Err(
            NestedTextError::at(
              InvalidIndentLevel,
              "invalid indentation.",
              line.lineno,
              line.depth + 1,
              line.text,
            ),
          )
        }
      } else {
        match line.kind {
          DictItem | KeyItem => self.read_dict(depth).map(v => Some(v))
          ListItem => self.read_list(depth).map(v => Some(v))
          StringItem => self.read_string(depth).map(v => Some(v))
          InlineList | InlineDict => {
            let consumed = self.lexer.next_line().unwrap()
            match consumed.value {
              Some(input) =>
                InlineParser::parse(
                  input,
                  consumed.lineno,
                  consumed.depth,
                  consumed.text,
                ).map(v => Some(v))
              None =>
                Err(
                  NestedTextError::new(UnrecognizedLine, "unrecognized line."),
                )
            }
          }
          Unrecognized =>
            Err(
              NestedTextError::at(
                UnrecognizedLine,
                "unrecognized line.",
                line.lineno,
                line.depth + 1,
                line.text,
              ),
            )
        }
      }
  }
}

///|
fn Parser::read_dict(
  self : Parser,
  depth : Int,
) -> Result[Value, NestedTextError] {
  let pairs : Array[(String, Value)] = []
  let seen_keys : Array[String] = []
  for ;; {
    match self.lexer.peek() {
      Some(line) =>
        if line.depth != depth {
          break
        } else {
          match line.kind {
            DictItem => {
              let consumed = self.lexer.next_line().unwrap()
              let key = consumed.key.unwrap()
              let raw_value = consumed.value.unwrap()
              if seen_keys.contains(key) {
                return Err(
                  NestedTextError::at(
                    DuplicateKey,
                    "duplicate key: \{key}.",
                    consumed.lineno,
                    1,
                    consumed.text,
                  ),
                )
              }
              seen_keys.push(key)
              let value = if !raw_value.is_empty() {
                match self.check_no_indented_content(depth) {
                  Err(err) => return Err(err)
                  Ok(_) => Value::String(raw_value)
                }
              } else {
                match self.read_indented_value(depth) {
                  Err(err) => return Err(err)
                  Ok(v) => v
                }
              }
              pairs.push((key, value))
            }
            KeyItem => {
              let first_key_lineno = line.lineno
              let first_key_text = line.text
              let key = self.read_key(depth)
              if seen_keys.contains(key) {
                return Err(
                  NestedTextError::new(DuplicateKey, "duplicate key: \{key}."),
                )
              }
              seen_keys.push(key)
              match self.lexer.peek() {
                Some(next_line) =>
                  if next_line.depth > depth {
                    let child_depth = next_line.depth
                    self.indent_stack.push(child_depth)
                    self.all_indent_levels.push(child_depth)
                    let value_result = self.read_value(child_depth)
                    ignore(self.indent_stack.pop())
                    match value_result {
                      Err(err) => return Err(err)
                      Ok(Some(v)) => pairs.push((key, v))
                      Ok(None) => pairs.push((key, Value::String("")))
                    }
                  } else {
                    return Err(
                      NestedTextError::at(
                        InvalidIndentLevel,
                        "multiline key requires a value.",
                        first_key_lineno,
                        depth + 1,
                        first_key_text,
                      ),
                    )
                  }
                None =>
                  return Err(
                    NestedTextError::new(
                      InvalidIndentLevel,
                      "indented value must follow multiline key.",
                    )
                    .with_lineno(first_key_lineno)
                    .with_line(first_key_text),
                  )
              }
            }
            _ =>
              return Err(
                NestedTextError::at(
                  UnexpectedLineType,
                  "expected dictionary item.",
                  line.lineno,
                  line.depth + 1,
                  line.text,
                ),
              )
          }
        }
      None => break
    }
  }
  Ok(Value::Dict(pairs))
}

///|
fn Parser::read_list(
  self : Parser,
  depth : Int,
) -> Result[Value, NestedTextError] {
  let items : Array[Value] = []
  for ;; {
    match self.lexer.peek() {
      Some(line) =>
        if line.depth != depth {
          break
        } else if line.kind == ListItem {
          let consumed = self.lexer.next_line().unwrap()
          let raw_value = consumed.value.unwrap()
          let value = if !raw_value.is_empty() {
            match self.check_no_indented_content(depth) {
              Err(err) => return Err(err)
              Ok(_) => Value::String(raw_value)
            }
          } else {
            match self.read_indented_value(depth) {
              Err(err) => return Err(err)
              Ok(v) => v
            }
          }
          items.push(value)
        } else {
          return Err(
            NestedTextError::at(
              UnexpectedLineType,
              "expected list item.",
              line.lineno,
              line.depth + 1,
              line.text,
            ),
          )
        }
      None => break
    }
  }
  Ok(Value::List(items))
}

///|
fn Parser::read_string(
  self : Parser,
  depth : Int,
) -> Result[Value, NestedTextError] {
  let parts : Array[String] = []
  while self.lexer.next_is(depth, StringItem) {
    let line = self.lexer.next_line().unwrap()
    parts.push(line.value.unwrap())
  }
  match self.lexer.peek() {
    Some(next) =>
      if next.depth > depth && next.kind == StringItem {
        Err(
          NestedTextError::at(
            InvalidIndentLevel,
            "invalid indentation.",
            next.lineno,
            depth + 1,
            next.text,
          ),
        )
      } else {
        Ok(Value::String(parts.join("\n")))
      }
    None => Ok(Value::String(parts.join("\n")))
  }
}

///|
fn Parser::read_key(self : Parser, depth : Int) -> String {
  let parts : Array[String] = []
  while self.lexer.next_is(depth, KeyItem) {
    let line = self.lexer.next_line().unwrap()
    parts.push(line.value.unwrap())
  }
  parts.join("\n")
}

///|
fn Parser::read_indented_value(
  self : Parser,
  parent_depth : Int,
) -> Result[Value, NestedTextError] {
  match self.lexer.peek() {
    Some(line) =>
      if line.depth > parent_depth {
        let child_depth = line.depth
        self.indent_stack.push(child_depth)
        self.all_indent_levels.push(child_depth)
        let result = self.read_value(child_depth)
        ignore(self.indent_stack.pop())
        match result {
          Err(err) => Err(err)
          Ok(Some(v)) => Ok(v)
          Ok(None) =>
            Err(NestedTextError::new(UnexpectedLineType, "expected value."))
        }
      } else {
        Ok(Value::String(""))
      }
    None => Ok(Value::String(""))
  }
}

///|
fn Parser::check_no_indented_content(
  self : Parser,
  parent_depth : Int,
) -> Result[Unit, NestedTextError] {
  match self.lexer.peek() {
    Some(next) =>
      if next.depth > parent_depth {
        Err(
          NestedTextError::at(
            InvalidIndentLevel,
            "invalid indentation.",
            next.lineno,
            parent_depth + 1,
            next.text,
          ),
        )
      } else {
        Ok(())
      }
    None => Ok(())
  }
}