section_delim = _{ "---" }
whitespace    = _{ " " | "\t" }

identifier = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }

collection_name        = { "name" ~ whitespace* ~ "=" ~ whitespace* ~ value ~ NEWLINE }
collection_description = { "description" ~ whitespace* ~ "=" ~ whitespace* ~ value }

variable = { "$" ~ whitespace* ~ key ~ whitespace* ~ "=" ~ whitespace* ~ value }

key   = { (!"=" ~ ANY)+ }
value = { (!"\n" ~ ANY)+ }

quoted_string = @{ ("\"" ~ ("\\\"" | !("\"" | NEWLINE) ~ ANY)* ~ "\"") | ("'" ~ ("\\'" | !("'" | NEWLINE) ~ ANY)* ~ "'") }
numeric_literal = @{ "-"? ~ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? }
boolean_literal = { "true" | "false" }
null_literal = { "null" }
scalar_literal = _{ quoted_string | numeric_literal | boolean_literal | null_literal }
primitive_type = { "string" | "integer" | "number" | "boolean" | "null" }

array_suffix = { "[]" }
nullable_suffix = { "?" }
type_atom = _{ primitive_type | identifier }
type_reference = { type_atom ~ array_suffix* ~ nullable_suffix? }

enum_predicate = { "enum" ~ whitespace* ~ "(" ~ whitespace* ~ scalar_literal ~ (whitespace* ~ "," ~ whitespace* ~ scalar_literal)* ~ whitespace* ~ ")" }
format_predicate = { "format" ~ whitespace* ~ "(" ~ whitespace* ~ identifier ~ whitespace* ~ ")" }
len_bound = @{ ASCII_DIGIT+ }
len_predicate = { "len" ~ whitespace* ~ "(" ~ whitespace* ~ len_bound? ~ whitespace* ~ ".." ~ whitespace* ~ len_bound? ~ whitespace* ~ ")" }
regex_literal = @{ "/" ~ ("\\/" | !("/" | NEWLINE) ~ ANY)* ~ "/" }
pattern_predicate = { "pattern" ~ whitespace* ~ "(" ~ whitespace* ~ regex_literal ~ whitespace* ~ ")" }
range_predicate = { "range" ~ whitespace* ~ "(" ~ whitespace* ~ numeric_literal? ~ whitespace* ~ ".." ~ whitespace* ~ numeric_literal? ~ whitespace* ~ ")" }
scalar_term = _{ enum_predicate | format_predicate | len_predicate | pattern_predicate | range_predicate | primitive_type | identifier }
scalar_expression = { scalar_term ~ (whitespace* ~ "&" ~ whitespace* ~ scalar_term)* }
scalar_declaration = { "scalar" ~ whitespace+ ~ identifier ~ whitespace* ~ "=" ~ whitespace* ~ scalar_expression }

field_optional = { "?" }
schema_field = { whitespace* ~ identifier ~ field_optional? ~ whitespace* ~ ":" ~ whitespace* ~ type_reference }
schema_object = { "{" ~ NEWLINE* ~ (schema_field ~ NEWLINE*)* ~ whitespace* ~ "}" }
schema_object_declaration = { "schema" ~ whitespace+ ~ identifier ~ whitespace* ~ schema_object }
schema_array_declaration = { "schema" ~ whitespace+ ~ identifier ~ whitespace* ~ "=" ~ whitespace* ~ type_reference }
declaration = { scalar_declaration | schema_object_declaration | schema_array_declaration }

preamble = _{ NEWLINE* ~ ((collection_name | collection_description | variable | header | query | callback | declaration) ~ NEWLINE*)+ }

http_method = { "GET" | "PUT" | "POST" | "PATCH" | "DELETE" | "OPTIONS" }

header = { "*" ~ whitespace* ~ key ~ whitespace* ~ "=" ~ whitespace* ~ value }

query = { "?" ~ whitespace* ~ key ~ whitespace* ~ "=" ~ whitespace* ~ value }
protocol_directive = { "protocol" ~ whitespace* ~ "=" ~ whitespace* ~ identifier }
session_directive = { "session" ~ whitespace* ~ "=" ~ whitespace* ~ value }
operation_directive = { "operation" ~ whitespace* ~ "=" ~ whitespace* ~ value }
variables_directive = { "variables" ~ whitespace* ~ "=" ~ whitespace* ~ value }
call_directive = { "call" ~ whitespace* ~ "=" ~ whitespace* ~ value }
tool_directive = { "tool" ~ whitespace* ~ "=" ~ whitespace* ~ value }
arguments_directive = { "arguments" ~ whitespace* ~ "=" ~ whitespace* ~ value }
send_directive = { "send" ~ whitespace* ~ "=" ~ whitespace* ~ value }
receive_directive = { "receive" }
within_directive = { "within" ~ whitespace* ~ "=" ~ whitespace* ~ value }
protocol_version_directive = { "protocol_version" ~ whitespace* ~ "=" ~ whitespace* ~ value }
client_name_directive = { "client_name" ~ whitespace* ~ "=" ~ whitespace* ~ value }
client_version_directive = { "client_version" ~ whitespace* ~ "=" ~ whitespace* ~ value }
capabilities_directive = { "capabilities" ~ whitespace* ~ "=" ~ whitespace* ~ value }

file = { "@" ~ whitespace* ~ value }
text = { value }
form = { "~" ~ whitespace* ~ key ~ whitespace* ~ "=" ~ whitespace* ~ (file | text) }

callback = { "!" ~ whitespace* ~ value }

response_capture = { "&" ~ whitespace* ~ (!NEWLINE ~ ANY)+ }

guard_content = { (!"]" ~ ANY)+ }
guard = _{ "[" ~ guard_content ~ "]" }
guard_prefix = _{ guard ~ whitespace* }

assertion = { guard_prefix? ~ "^" ~ whitespace* ~ (!NEWLINE ~ ANY)+ }

fragment_path = { (!NEWLINE ~ ANY)+ }
fragment_include = { guard_prefix? ~ "<<" ~ whitespace* ~ fragment_path }

dependency = { ">" ~ whitespace* ~ "requires" ~ whitespace* ~ ":" ~ whitespace* ~ (!NEWLINE ~ ANY)+ }

body_content_type =  { value }
body              =  { (!"~~~" ~ ANY)+ }
body_block        = _{ "~~~" ~ whitespace* ~ body_content_type* ~ NEWLINE ~ body* ~ "~~~" }

url = { (!"\n" ~ ANY)+ }

description = { !section_delim ~ !http_method ~ value }

request_target = _{ http_method ~ whitespace+ ~ url }

request_item = _{ variable | dependency | protocol_directive | session_directive | operation_directive | variables_directive | call_directive | tool_directive | arguments_directive | send_directive | receive_directive | within_directive | protocol_version_directive | client_name_directive | client_version_directive | capabilities_directive | header | query | body_block | form | callback | response_capture | assertion | fragment_include | request_target | description }

request = {
    request_item ~ (NEWLINE+ ~ request_item)* ~ NEWLINE*
}

requests = {
    request ~ (section_delim ~ NEWLINE+ ~ request)*
}

legacy_collection_line = _{ !(request_target | protocol_directive | session_directive | operation_directive | variables_directive | call_directive | tool_directive | arguments_directive | send_directive | receive_directive | within_directive | protocol_version_directive | client_name_directive | client_version_directive | capabilities_directive | variable | dependency | header | query | form | callback | response_capture | assertion | fragment_include) ~ !section_delim ~ (!NEWLINE ~ ANY)+ }
legacy_collection_header = _{ legacy_collection_line ~ (NEWLINE+ ~ legacy_collection_line)* ~ NEWLINE* ~ section_delim ~ NEWLINE* }

request_collection = { (preamble ~ section_delim ~ NEWLINE*)* ~ legacy_collection_header? ~ (section_delim ~ NEWLINE*)? ~ requests ~ NEWLINE* ~ EOI }
