///|
/// Text ingest for local Protocol Contract fixtures.

///|
pub(all) suberror ProtocolIngestError {
  ParseFailed(String)
  MissingField(String)
  InvalidField(String, String)
} derive(Debug)

///|
pub fn parse_global_protocol_text(
  text : String,
) -> GlobalProtocol raise ProtocolIngestError {
  let trimmed = text[:].trim()
  if trimmed.has_prefix("{") {
    parse_global_protocol_json(text)
  } else {
    parse_global_protocol_yaml(text)
  }
}

///|
pub fn parse_global_protocol_json(
  text : String,
) -> GlobalProtocol raise ProtocolIngestError {
  let parsed = @json.parse(text[:]) catch {
    err => raise ProtocolIngestError::ParseFailed(err.to_string())
  }
  let protocol_id = json_required_string(parsed, "protocol_id")
  let mut protocol = GlobalProtocol::new(protocol_id~)
  match parsed {
    { "messages": Array(messages), .. } =>
      for message_json in messages {
        protocol = append_message_json(protocol, message_json)
      }
    _ => raise ProtocolIngestError::MissingField("messages")
  }
  match parsed {
    { "terminal_labels": Array(labels), .. } =>
      for label_json in labels {
        protocol = protocol.with_terminal_label(
          label=decode_json_string(label_json, "terminal_labels"),
        )
      }
    _ => ()
  }
  protocol
}

///|
pub fn parse_global_protocol_yaml(
  text : String,
) -> GlobalProtocol raise ProtocolIngestError {
  let fields = parse_key_value_lines(text)
  let protocol_id = required_field(fields, "protocol_id")
  let mut protocol = GlobalProtocol::new(protocol_id~)
  for field in fields {
    let (key, value) = field
    if key == "message" {
      protocol = append_message_row(protocol, value)
    } else if key == "terminal_label" {
      protocol = protocol.with_terminal_label(label=value)
    }
  }
  protocol
}

///|
fn append_message_json(
  protocol : GlobalProtocol,
  json : Json,
) -> GlobalProtocol raise ProtocolIngestError {
  protocol.send(
    from=parse_role_name(json_required_string(json, "from"), "from"),
    to=parse_role_name(json_required_string(json, "to"), "to"),
    label=json_required_string(json, "label"),
    object_id=json_required_string(json, "object_id"),
  )
}

///|
fn append_message_row(
  protocol : GlobalProtocol,
  value : String,
) -> GlobalProtocol raise ProtocolIngestError {
  let parts = value.split(",").to_array()
  guard parts.length() == 4 else {
    raise ProtocolIngestError::InvalidField("message", value)
  }
  protocol.send(
    from=parse_role_name(clean_value(parts[0].trim().to_owned()), "from"),
    to=parse_role_name(clean_value(parts[1].trim().to_owned()), "to"),
    label=clean_value(parts[2].trim().to_owned()),
    object_id=clean_value(parts[3].trim().to_owned()),
  )
}

///|
fn parse_role_name(
  value : String,
  field : String,
) -> Role raise ProtocolIngestError {
  let clean = clean_value(value)
  if clean == "" {
    raise ProtocolIngestError::InvalidField(field, clean)
  }
  role(name=clean)
}

///|
fn parse_key_value_lines(
  text : String,
) -> Array[(String, String)] raise ProtocolIngestError {
  let fields : Array[(String, String)] = []
  for raw_line in text.split("\n") {
    let line = raw_line.trim().to_owned()
    if line == "" || line.has_prefix("#") || line == "---" {
      ()
    } else {
      fields.push(parse_key_value_line(line))
    }
  }
  fields
}

///|
fn parse_key_value_line(
  line : String,
) -> (String, String) raise ProtocolIngestError {
  let parts = if line.contains(":") {
    line.split(":").to_array()
  } else {
    line.split("=").to_array()
  }
  guard parts.length() == 2 else {
    raise ProtocolIngestError::ParseFailed("invalid line: " + line)
  }
  (
    normalized(parts[0].trim().to_owned()),
    clean_value(parts[1].trim().to_owned()),
  )
}

///|
fn required_field(
  fields : Array[(String, String)],
  name : String,
) -> String raise ProtocolIngestError {
  match optional_field(fields, name) {
    Some(value) => value
    None => raise ProtocolIngestError::MissingField(name)
  }
}

///|
fn optional_field(fields : Array[(String, String)], name : String) -> String? {
  let mut found : String? = None
  for field in fields {
    let (key, value) = field
    if key == name {
      found = Some(value)
    }
  }
  found
}

///|
fn json_required_string(
  json : Json,
  field : String,
) -> String raise ProtocolIngestError {
  match json_optional_string(json, field) {
    Some(value) => value
    None => raise ProtocolIngestError::MissingField(field)
  }
}

///|
fn json_optional_string(
  json : Json,
  field : String,
) -> String? raise ProtocolIngestError {
  match (field, json) {
    ("protocol_id", { "protocol_id": value, .. }) =>
      Some(decode_json_string(value, field))
    ("from", { "from": value, .. }) => Some(decode_json_string(value, field))
    ("to", { "to": value, .. }) => Some(decode_json_string(value, field))
    ("label", { "label": value, .. }) => Some(decode_json_string(value, field))
    ("object_id", { "object_id": value, .. }) =>
      Some(decode_json_string(value, field))
    _ => None
  }
}

///|
fn decode_json_string(
  json : Json,
  field : String,
) -> String raise ProtocolIngestError {
  @json.from_json(json) catch {
    _ => raise ProtocolIngestError::InvalidField(field, json.stringify())
  }
}

///|
fn clean_value(value : String) -> String {
  let trimmed = value[:].trim().to_owned()
  let without_comma = if trimmed.has_suffix(",") {
    trimmed[:trimmed.length() - 1].to_owned()
  } else {
    trimmed
  }
  if without_comma.length() >= 2 &&
    (
      (without_comma.has_prefix("\"") && without_comma.has_suffix("\"")) ||
      (without_comma.has_prefix("'") && without_comma.has_suffix("'"))
    ) {
    without_comma[1:without_comma.length() - 1].to_owned()
  } else {
    without_comma
  }
}

///|
fn normalized(value : String) -> String {
  clean_value(value).to_lower()
}