module generator

import (
  std.alloc
  std.collections
  std.strings
)

let valid_identifier(value: str): bool {
  if @len(value) == 0 { return false }
  for byte, index in value {
    let letter = (byte >= 'a' && byte <= 'z') || (byte >= 'A' && byte <= 'Z')
    let digit = byte >= '0' && byte <= '9'
    if !(letter || byte == '_' || (index > 0 && digit)) { return false }
  }
  return true
}

let parse_value(text: str): (Value, bool) {
  let value = text.trim()
  if @len(value) >= 2 && value[0] == '"' && value[@len(value) - 1] == '"' {
    return .String(value[1:@len(value) - 1]), true
  }
  if value.eq("true") { return .Boolean(true), true }
  if value.eq("false") { return .Boolean(false), true }
  let number, valid = std.strings.parse_i64(value)
  if valid { return .Number(number), true }
  return .String(""), false
}

let parse_document(
  engine: *mut Engine,
  source: str,
  path: str,
): (Document, bool) {
  let mut fields = std.collections.DynamicArray(Field).init(engine.allocator)
  let mut body = source
  let has_front_matter = source.starts_with("---\n") || source.starts_with("---\r\n")
  if !has_front_matter { return .{ fields=fields, body=body }, true }

  let first_newline, _ = source.find("\n")
  let mut cursor = first_newline + 1
  let mut closed = false
  for cursor <= @len(source) {
    let relative_end, found_newline = source[cursor:].find("\n")
    let end = if found_newline { cursor + relative_end } else { @len(source) }
    let line = source[cursor:end].trim_end()
    if line.eq("---") {
      body = if found_newline { source[end + 1:] } else { "" }
      closed = true
      break
    }
    if @len(line.trim()) != 0 {
      let equal, found_equal = line.find("=")
      if !found_equal {
        fail_path(engine, "invalid front matter in ", path)
        return .{ fields=fields, body=body }, false
      }
      let key = line[:equal].trim()
      let raw_value = line[equal + 1:].trim()
      if !valid_identifier(key) {
        fail_path(engine, "invalid front matter key in ", path)
        return .{ fields=fields, body=body }, false
      }
      let value, valid = parse_value(raw_value)
      if !valid {
        fail_path(engine, "invalid front matter value in ", path)
        return .{ fields=fields, body=body }, false
      }
      let mut replaced = false
      for existing.&mut in fields.as_mut_slice() {
        if existing.key.eq(key) {
          existing.* = Field.{ key=key, value=value }
          replaced = true
          break
        }
      }
      if !replaced && !fields.append(Field.{ key=key, value=value }) {
        fail(engine, "out of memory")
        return .{ fields=fields, body=body }, false
      }
    }
    if !found_newline { break }
    cursor = end + 1
  }
  if !closed {
    fail_path(engine, "unterminated front matter in ", path)
    return .{ fields=fields, body=body }, false
  }
  return .{ fields=fields, body=body }, true
}