///|
/// Parse the small, portable response-file language used by compiler drivers.
/// It supports whitespace, single/double quotes, backslash escapes, and lines
/// beginning with `#` comments. Shell expansion is intentionally out of scope.
pub fn parse_response_file(text : String) -> Result[Array[String], String] {
  let arguments : Array[String] = []
  let mut current = ""
  let mut escaped = false
  let mut quote : Char? = None
  let mut at_token_start = true
  let mut in_comment = false

  fn flush() -> Unit {
    if current != "" {
      arguments.push(current)
      current = ""
    }
    at_token_start = true
  }

  for ch in text {
    if in_comment {
      if ch == '\n' {
        in_comment = false
        at_token_start = true
      } else {
        continue
      }
    }
    match quote {
      Some(mark) => {
        if escaped {
          current += "\{ch}"
          escaped = false
        } else if ch == '\\' {
          escaped = true
        } else if ch == mark {
          quote = None
        } else {
          current += "\{ch}"
        }
        at_token_start = false
      }
      None =>
        if escaped {
          current += "\{ch}"
          escaped = false
          at_token_start = false
        } else if ch == '\\' {
          escaped = true
          at_token_start = false
        } else if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' {
          flush()
        } else if ch == '#' && at_token_start {
          current = ""
          at_token_start = true
          in_comment = true
        } else if ch == '"' || ch == '\'' {
          quote = Some(ch)
          at_token_start = false
        } else {
          current += "\{ch}"
          at_token_start = false
        }
    }
  }
  if escaped {
    return Err("response file ends with an escape")
  }
  match quote {
    Some(_) => Err("response file has an unterminated quote")
    None => {
      flush()
      Ok(arguments)
    }
  }
}

///|
fn response_file_token(text : String) -> String {
  text[1:].to_owned()
}

///|
fn materialize_response_tokens(
  tokens : Array[String],
  files : Map[String, String],
  depth : Int,
) -> Result[Array[String], String] {
  if depth > 8 {
    return Err("response file nesting exceeded the maximum depth of 8")
  }
  let result : Array[String] = []
  for token in tokens {
    if token.length() > 1 && token[0] == '@' {
      let path = response_file_token(token)
      match files.get(path) {
        None => return Err("response file is missing: " + path)
        Some(contents) =>
          match parse_response_file(contents) {
            Err(error) => return Err(path + ": " + error)
            Ok(nested) =>
              match materialize_response_tokens(nested, files, depth + 1) {
                Err(error) => return Err(error)
                Ok(expanded) =>
                  for item in expanded {
                    result.push(item)
                  }
              }
          }
      }
    } else {
      result.push(token)
    }
  }
  Ok(result)
}

///|
/// Expand `@file` references from an in-memory response-file table. Keeping
/// file contents at the boundary makes this API usable on WASM-GC and JS.
pub fn materialize_response_command(
  command : String,
  files : Map[String, String],
) -> Result[Array[String], String] {
  match parse_response_file(command) {
    Err(error) => Err(error)
    Ok(tokens) => materialize_response_tokens(tokens, files, 0)
  }
}