use std::process;

const VERSION: &str = env!("CARGO_PKG_VERSION");

// Prints `s` as data — braces in `s` are never interpreted as format tokens.
fn text(s: &str) {
    println!("{s}");
}

fn section(title: &str) {
    println!();
    text(title);
    println!();
}

// ── public API ────────────────────────────────────────────────────────────────

pub fn print_help() {
    println!("DefLang {VERSION} — a scripting language for HTTP workflows");
    println!();
    text("Usage: def <command> [file]");
    println!();
    text("Commands:");
    text("  run   <file>   Execute a .def script");
    text("  check <file>   Validate without executing HTTP calls (dry-run)");
    text("  fmt   <file>   Format a .def script (not yet implemented)");
    text("  help  [topic]  Show language help topics");
    println!();
    text("Options:");
    text("  --version   Print the version number");
    text("  --help      Show this help message");
    println!();
    text("Topics:");
    text("  array          Ordered collection of values with indexed access and iteration");
    text("  const          Immutable variable bindings — prevent reassignment after definition");
    text("  range          Generate an array of consecutive integers with range(start..end)");
    text("  assert         Abort execution when a boolean expression is false");
    text("  body           Request bodies from .jdef (JSON) and .tdef (text) template files");
    text("  check          Validate syntax without executing (dry-run mode)");
    text("  conditionals   if/else control flow with block scope");
    text("  loops          for and while do loops with break() and next()");
    text("  datetime       Current date and time with formatting and individual part access");
    text("  delay          Pause execution for a given number of milliseconds");
    text("  envvars        Load environment variable defaults from .edef files");
    text("  expect         Assert response conditions with readable error messages");
    text("  inspect        Print request and response details for debugging");
    text("  params         Pass runtime parameters to scripts via --param KEY=VALUE");
    text("  float          Floating-point numbers and arithmetic operators");
    text("  function       Define and call named user functions");
    text("  headers        Request headers inline or from .hdef template files");
    text("  imported       Load another .def file as a reusable module");
    text("  integer        Integer numbers and arithmetic operators");
    text("  match          Pattern matching against literal values");
    text("  mock           Intercept HTTP requests with pre-configured responses");
    text("  query_string   URL query parameters inline or from .qdef template files");
    text("  request        Build and execute HTTP requests with a fluent API");
    text("  form           Submit HTML forms with .form_from() and .fdef files");
    text("  html           Query HTML responses with CSS selectors and regex");
    text("  json           JSON path assertions on response bodies");
    text("  response       Inspect HTTP response status, headers, and body");
    text("  retry          Retry, backoff strategies, and per-attempt timeout");
    text("  snapshot       Save response snapshot to disk; validate structure with assert_snapshot()");
    text("  string         Text values, multiline strings, and string operations");
    text("  tuple          Key/value pairs used for headers and query parameters");
    println!();
    text("Aliases:");
    text("  hdef                         → headers");
    text("  qdef                         → query_string");
    text("  jdef, tdef, text             → body");
    text("  json_assertions, jsonpath    → json");
    println!();
    text("Run 'def help <topic>' for details and examples.");
    text("Run 'def help about' for information about DefLang.");
}

pub fn print_topic(topic: &str) {
    match topic {
        "about" => print_about(),
        "array" => print_array(),
        "const" => print_const(),
        "range" => print_range(),
        "assert" => print_assert(),
        "body" | "jdef" | "tdef" | "text" => print_body(),
        "check" => print_check(),
        "conditionals" => print_conditionals(),
        "loops" | "while" | "for" => print_loops(),
        "datetime" => print_datetime(),
        "delay" => print_delay(),
        "envvars" => print_envvars(),
        "expect" => print_expect(),
        "inspect" => print_inspect(),
        "json" | "json_assertions" | "jsonpath" => print_json(),
        "float" => print_float(),
        "params" | "from_cmd_param" | "cmdparam" => print_params(),
        "function" => print_function(),
        "headers" | "hdef" => print_headers(),
        "form" | "fdef" => print_form(),
        "html" => print_html(),
        "imported" => print_imported(),
        "integer" => print_integer(),
        "match" => print_match(),
        "mock" => print_mock(),
        "query_string" | "qdef" => print_query_string(),
        "request" => print_request(),
        "response" => print_response(),
        "retry" => print_retry(),
        "snapshot" => print_snapshot(),
        "string" => print_string(),
        "tuple" => print_tuple(),
        other => {
            eprintln!("unknown help topic '{other}'");
            eprintln!("Run 'def help' for a list of all topics.");
            process::exit(1);
        }
    }
}

// ── about ─────────────────────────────────────────────────────────────────────

fn print_about() {
    text("DEFLANG");
    section("DESCRIPTION");
    text("  DefLang is a scripting language for HTTP workflows. It provides a typed,");
    text("  readable syntax for building requests, validating responses, and chaining");
    text("  multiple API calls — designed as a programmable alternative to tools like");
    text("  Postman or curl scripts.");
    println!();
    text("  The def keyword declares variables and functions. The language is typed,");
    text("  sequential, and designed to read like a test script. Each .def file is");
    text("  an executable workflow that runs top to bottom.");
    section("LANGUAGE PIPELINE");
    text("  Lexer → Parser → AST → Interpreter");
    text("  Written in Rust.");
    section("TYPES");
    text("  integer    64-bit signed integer");
    text("  float      64-bit floating-point number");
    text("  string     UTF-8 text");
    text("  boolean    true or false");
    text("  array      Ordered collection of values");
    text("  tuple      Key/value pair");
    text("  datetime   Current system date and time");
    text("  request    HTTP request builder");
    text("  response   HTTP response with status, headers, and body");
    section("FEATURES");
    text("  · User-defined functions with typed parameters");
    text("  · Module system via imported()");
    text("  · if/else, for, match control flow with block scope");
    text("  · Full HTTP client: GET, POST, PUT, PATCH, DELETE");
    text("  · Template files for headers (.hdef), query strings (.qdef),");
    text("    JSON bodies (.jdef), and text bodies (.tdef)");
    text("  · Environment variable loading from .edef files");
    text("  · String interpolation in print() with {{expression}} placeholders");
    section("INSTALL");
    text("  cargo install deflang");
    section("SOURCE");
    text("  https://github.com/mfcastellani/def");
    println!();
    text("  by Marcelo Castellani, 2026");
}

// ── topics ────────────────────────────────────────────────────────────────────

fn print_check() {
    text("CHECK  (--check)");
    section("DESCRIPTION");
    text("  Dry-run mode: executes the full script without making real HTTP requests.");
    text("  Catches syntax errors, undefined variables, unknown methods, wrong argument");
    text("  counts, and type errors. HTTP calls return a stub 200 response. print() and");
    text("  delay() are suppressed. Imports are loaded and validated recursively.");
    text("  Exits with code 0 on success, 1 on error.");
    section("SYNTAX");
    text("  def check <file.def>");
    section("WHAT IS CAUGHT");
    text("  Lexer/parser errors   Syntax problems, invalid tokens");
    text("  Undefined variables   References to variables that were never declared");
    text("  Unknown methods       Calling methods that do not exist on a value");
    text("  Wrong argument count  Calling functions with the wrong number of arguments");
    text("  Type errors           Assigning incompatible types");
    section("WHAT IS NOT CAUGHT");
    text("  HTTP response values  Assertions on response status, body, or headers");
    text("  Network errors        Connection failures or timeouts");
    section("EXAMPLE");
    text("  def check workflow.def");
    text("  # workflow.def: syntax ok");
    println!();
    text("  def check broken.def");
    text("  # runtime error: unknown request method 'retry' at line 5 in 'broken.def'");
}

fn print_array() {
    text("ARRAY");
    section("DESCRIPTION");
    text("  Ordered collection of values with indexed access, iteration, and mutation.");
    text("  Arrays can hold mixed value types.");
    section("SYNTAX");
    text("  def items as array                               // empty");
    text("  def names as array(\"Marcelo\", \"Nicolas\")         // initialized");
    text("  def names as const array(\"GET\", \"POST\")          // immutable — push() is a runtime error");
    text("  names.push(\"Ana\")                                // append");
    text("  names[0]                                         // index access");
    text("  names.get(1)                                     // method access");
    section("METHODS");
    text("  len()          Number of elements");
    text("  is_empty()     True when the array has no elements");
    text("  get(index)     Value at the given 0-based index");
    text("  push(value)    Append a value to the end");
    section("EXAMPLE");
    text("  def names as array(\"Marcelo\", \"Ana\", \"Nicolas\")");
    text("  names.push(\"Felicia\")");
    println!();
    text("  print(names.len())    // 4");
    text("  print(names[0])       // Marcelo");
    text("  print(names.get(1))   // Ana");
    println!();
    text("  for name in names (");
    text("    print(name)");
    text("  )");
    println!();
    text("  assert(names.len() == 4)");
    text("  assert(names[0] == \"Marcelo\")");
    text("  assert(names.is_empty() == false)");
}

fn print_range() {
    text("RANGE");
    section("DESCRIPTION");
    text("  range(start..end) generates an array of consecutive integers from start to end,");
    text("  inclusive on both ends. Bounds must be integers. The result is a regular array");
    text("  and can be iterated with for, assigned to an array variable, or passed anywhere");
    text("  an array is expected.");
    section("SYNTAX");
    text("  range(start..end)");
    println!();
    text("  start and end are integer expressions. end must be >= start.");
    section("EXAMPLE");
    text("  // iterate with for");
    text("  def total as integer(0)");
    text("  for n in range(1..10) (");
    text("    total += n");
    text("  )");
    text("  assert(total == 55)");
    println!();
    text("  // assign to array variable");
    text("  def nums as array");
    text("  nums = range(1..5)");
    text("  assert(nums.len() == 5)");
    text("  assert(nums.get(0) == 1)");
    text("  assert(nums.get(4) == 5)");
    println!();
    text("  // variable bounds");
    text("  def start as integer(3)");
    text("  def stop  as integer(7)");
    text("  def slice as array");
    text("  slice = range(start..stop)   // [3, 4, 5, 6, 7]");
    section("ERRORS");
    text("  · start > end          → runtime error: range start (...) must not be greater than end");
    text("  · non-integer bounds   → runtime error: range bounds must be integers");
    text("  · wrong argument form  → runtime error: range expects a range expression like range(1..5)");
}

fn print_assert() {
    text("ASSERT");
    section("DESCRIPTION");
    text("  Evaluates a boolean expression and aborts execution with an error if the");
    text("  result is false. Use assert to validate API responses and intermediate state.");
    text("  A failed assertion prints 'runtime error: assertion failed' and exits.");
    section("SYNTAX");
    text("  assert(boolean_expression)");
    section("EXAMPLE");
    text("  assert(1 + 2 == 3)");
    text("  assert(true)");
    text("  assert(not false)");
    println!();
    text("  def x as integer(10)");
    text("  assert(x > 0)");
    text("  assert(x == 10)");
    println!();
    text("  def res as response(request(GET).path(\"https://httpbingo.org/get\").do())");
    text("  assert(res.ok())");
    text("  assert(res.status() == 200)");
    text("  assert(res.body_contains(\"headers\"))");
}

fn print_body() {
    text("BODY  (aliases: jdef, tdef, json, text)");
    section("DESCRIPTION");
    text("  Sends a request body loaded from a template file. Two formats are supported:");
    text("  .jdef for JSON and .tdef for plain text. Both support {{variable}} placeholder");
    text("  substitution via with_var(). The Content-Type header is set automatically.");
    section(".JDEF FORMAT");
    text("  Raw JSON with {{variable}} placeholders. No comments.");
    println!();
    text("  {");
    text("    \"title\":  \"{{title}}\",");
    text("    \"userId\": {{user_id}}");
    text("  }");
    println!();
    text("  Note: bare {{user_id}} (no quotes) renders as a JSON number.");
    section(".TDEF FORMAT");
    text("  Plain text with {{variable}} placeholders.");
    text("  Lines starting with // or # are treated as comments and stripped before sending.");
    println!();
    text("  // request body");
    text("  name: {{name}}");
    text("  purpose: HTTP testing");
    section("SYNTAX");
    text("  request(POST)");
    text("    .path(url)");
    text("    .body_from(\"path/to/file.jdef\")  // Content-Type inferred from extension");
    text("    .type(JSON)                      // override Content-Type (optional)");
    text("    .with_var(variable)              // registers a variable for substitution");
    text("    .do()");
    println!();
    text("  .jdef files set Content-Type: application/json automatically.");
    text("  .tdef files set Content-Type: text/plain automatically.");
    text("  Use .type(JSON) or .type(TEXT) to override the inferred type.");
    section("EXAMPLE");
    text("  def title   as string(\"DefLang post\")");
    text("  def user_id as string(\"1\")");
    println!();
    text("  def res as response(");
    text("    request(POST)");
    text("      .path(\"https://jsonplaceholder.typicode.com/posts\")");
    text("      .body_from(\"jdef/post.jdef\")");
    text("      .with_var(title)");
    text("      .with_var(user_id)");
    text("      .do()");
    text("  )");
    println!();
    text("  assert(res.status() == 201)");
    text("  print(\"created: {{res.describe_status()}}\")");
}

fn print_conditionals() {
    text("CONDITIONALS");
    section("DESCRIPTION");
    text("  if/else control flow. The condition must evaluate to a boolean.");
    text("  Blocks create a local scope: variables declared with def inside a block");
    text("  are not visible outside it. Assignments update the nearest enclosing");
    text("  variable — local if it exists in scope, global otherwise.");
    text("  The else branch is optional.");
    section("SYNTAX");
    text("  if condition (");
    text("    // then branch");
    text("  ) else (");
    text("    // else branch");
    text("  )");
    section("EXAMPLE");
    text("  def status  as integer(200)");
    text("  def message as string()");
    println!();
    text("  if status == 200 (");
    text("    message = \"ok\"");
    text("  ) else (");
    text("    message = \"unexpected\"");
    text("  )");
    println!();
    text("  assert(message == \"ok\")");
    println!();
    text("  // Conditional response handling");
    text("  def res as response(request(GET).path(\"https://httpbingo.org/get\").do())");
    println!();
    text("  if res.ok() (");
    text("    print(\"success: {{res.describe_status()}}\")");
    text("    assert(res.body_contains(\"headers\"))");
    text("  ) else (");
    text("    print(\"failure: {{res.describe_status()}}\")");
    text("  )");
}

fn print_loops() {
    text("LOOPS");
    section("DESCRIPTION");
    text("  DefLang has two loop constructs: for (collection iteration) and while do");
    text("  (condition-based repetition). Both support break() to exit early and next()");
    text("  to skip the rest of the current iteration. Both loops create a local scope:");
    text("  variables declared with def inside a loop body are not visible outside it.");
    section("FOR — iterate over a collection");
    text("  for <variable> in <array> (");
    text("    // body — variable is bound to each element");
    text("  )");
    println!();
    text("  def items as array(10, 20, 30)");
    text("  def total as integer(0)");
    text("  for item in items (");
    text("    total += item");
    text("  )");
    text("  assert(total == 60)");
    section("WHILE DO — repeat while a condition is true");
    text("  while <boolean expression> do (");
    text("    // body — runs as long as condition is true");
    text("  )");
    println!();
    text("  def i as integer(1)");
    text("  def sum as integer(0)");
    text("  while i <= 100 do (");
    text("    sum += i");
    text("    i += 1");
    text("  )");
    text("  assert(sum == 5050)");
    section("BREAK — exit the loop immediately");
    text("  break() stops the current loop and resumes after its closing ).");
    println!();
    text("  def n as integer(0)");
    text("  while true do (");
    text("    n += 1");
    text("    if n == 5 (");
    text("      break()");
    text("    )");
    text("  )");
    text("  assert(n == 5)");
    section("NEXT — skip the rest of the current iteration");
    text("  next() jumps to the next iteration, skipping remaining statements in the body.");
    println!();
    text("  def items as array(1, 2, 3, 4, 5)");
    text("  def evens as array");
    text("  for item in items (");
    text("    if item % 2 != 0 (");
    text("      next()");
    text("    )");
    text("    evens.push(item)");
    text("  )");
    text("  assert(evens.len() == 2)");
    section("NOTES");
    text("  · break() and next() take no arguments.");
    text("  · Calling break() or next() outside a loop is a runtime error.");
    text("  · Calling break() or next() inside a user function called from a loop");
    text("    is a runtime error — they do not propagate across function boundaries.");
}

fn print_datetime() {
    text("DATETIME");
    section("DESCRIPTION");
    text("  Captures the current system date and time at the moment the variable is");
    text("  declared. Individual parts can be read and updated. Setters return the");
    text("  updated datetime value. Useful for timestamping workflow runs.");
    section("FORMAT TOKENS");
    text("  hh     Hour (24h)");
    text("  mm     Minutes (when after hh) or month (when after dd)");
    text("  ss     Seconds");
    text("  dd     Day");
    text("  yy     Two-digit year");
    text("  yyyy   Four-digit year");
    section("METHODS");
    text("  format(mask)   Format as string using the tokens above");
    text("  hour()         Read or set the hour component");
    text("  minute()       Read or set the minute component");
    text("  second()       Read or set the second component");
    text("  day()          Read or set the day component");
    text("  month()        Read or set the month component");
    text("  year()         Read or set the year component");
    println!();
    text("  Calling any part method with an integer argument sets that component.");
    section("SYNTAX");
    text("  def now   as datetime               // current system time");
    text("  def epoch as const datetime         // immutable — setter calls are a runtime error");
    section("EXAMPLE");
    text("  def now as datetime");
    println!();
    text("  print(now.format(\"hh:mm:ss dd/mm/yyyy\"))");
    println!();
    text("  now.year(2026)");
    text("  now.month(1)");
    text("  now.day(1)");
    println!();
    text("  print(\"{{now.day()}}/{{now.month()}}/{{now.year()}}\")  // 1/1/2026");
    println!();
    text("  // Capture start and end timestamps");
    text("  def start_time  as datetime");
    text("  def started_at  as string(start_time.format(\"hh:mm:ss\"))");
    text("  // ... workflow ...");
    text("  def end_time    as datetime");
    text("  def finished_at as string(end_time.format(\"hh:mm:ss\"))");
    println!();
    text("  print(\"started:  {{started_at}}\")");
    text("  print(\"finished: {{finished_at}}\")");
}

fn print_delay() {
    text("DELAY");
    section("DESCRIPTION");
    text("  Pauses execution for a given number of milliseconds.");
    text("  The argument must be a non-negative integer.");
    text("  Useful for rate limiting between repeated API calls.");
    section("SYNTAX");
    text("  delay(milliseconds)");
    section("EXAMPLE");
    text("  print(\"Sending request...\")");
    text("  delay(500)");
    text("  def res as response(request(GET).path(\"https://httpbingo.org/get\").do())");
    text("  assert(res.ok())");
    println!();
    text("  // Pause between calls in a loop");
    text("  def ids  as array(\"1\", \"2\", \"3\")");
    text("  def base as string(\"https://api.example.com/posts/\")");
    println!();
    text("  for id in ids (");
    text("    def url as string(concat(base, id))");
    text("    def res as response(request(GET).path(url).do())");
    text("    print(\"{{id}}: {{res.describe_status()}}\")");
    text("    delay(200)");
    text("  )");
}

fn print_envvars() {
    text("ENVVARS");
    section("DESCRIPTION");
    text("  Loads environment variable defaults from a .edef file into the process");
    text("  environment. Values are then read with from_env_var() on a string.");
    println!();
    text("  Resolution order:");
    text("    1. If the variable is already set in the system environment, it is used");
    text("       as-is and the .edef value is ignored (a warning is printed to stderr).");
    text("    2. Otherwise the value from the .edef file is loaded and used.");
    text("    3. If the variable is set in neither place, execution aborts with:");
    text("       runtime error: environment variable 'X' is not set");
    section(".EDEF FORMAT");
    text("  One NAME=value per line. // and # lines are comments.");
    println!();
    text("  # API settings");
    text("  API_HOST=https://api.example.com");
    text("  API_KEY=dev-key-1234");
    text("  TIMEOUT=5000");
    section("SYNTAX");
    text("  def env  as envvars(\"edef/settings.edef\")");
    text("  def host as string().from_env_var(\"API_HOST\")");
    section("EXAMPLE");
    text("  def env     as envvars(\"edef/settings.edef\")");
    text("  def host    as string().from_env_var(\"API_HOST\")");
    text("  def api_key as string().from_env_var(\"API_KEY\")");
    println!();
    text("  // Override at runtime without modifying the file:");
    text("  // API_KEY=prod-secret def workflow.def");
    println!();
    text("  def res as response(");
    text("    request(GET)");
    text("      .path(concat(host, \"/users\"))");
    text("      .header(tuple(\"Authorization\", concat(\"Bearer \", api_key)))");
    text("      .do()");
    text("  )");
    println!();
    text("  assert(res.ok())");
    text("  print(\"status: {{res.describe_status()}}\")");
}

fn print_float() {
    text("FLOAT");
    section("DESCRIPTION");
    text("  64-bit floating-point numbers. Arithmetic operators +, -, *, /, % are");
    text("  supported. Mixed integer/float expressions produce float results.");
    section("SYNTAX");
    text("  def price as float(10.5)");
    text("  def price as float              // defaults to 0.0");
    text("  def price as const float(10.5)  // immutable — reassignment is a runtime error");
    section("OPERATORS");
    text("  +    Addition");
    text("  -    Subtraction");
    text("  *    Multiplication");
    text("  /    Division");
    text("  %    Modulo");
    text("  +=   Add and assign");
    text("  -=   Subtract and assign");
    section("METHODS");
    text("  random(min, max)   Return a random float in [min, max] (inclusive)");
    text("                     Arguments may be float or integer.");
    text("  to_string()        Convert the float to its string representation");
    section("EXAMPLE");
    text("  def price as float(19.99)");
    text("  def tax   as float(0.1)");
    text("  def total as float(price + price * tax)");
    println!();
    text("  print(\"price: {{price}}\")");
    text("  print(\"total: {{total}}\")");
    println!();
    text("  assert(total > price)");
    text("  assert(total < 30.0)");
    println!();
    text("  def discount   as float(0.15)");
    text("  def discounted as float(price - price * discount)");
    println!();
    text("  assert(discounted < price)");
    text("  assert(discounted > 0.0)");
    println!();
    text("  // Random float in a range");
    text("  def prob as float().random(0.0, 1.0)");
    text("  assert(prob >= 0.0)");
    text("  assert(prob <= 1.0)");
    println!();
    text("  // Convert to string");
    text("  def f as float(9.5)");
    text("  def s as string(f.to_string())");
    text("  assert(s == \"9.5\")");
}

fn print_function() {
    text("FUNCTION");
    section("DESCRIPTION");
    text("  Defines a named reusable function with typed parameters.");
    text("  Functions do not use 'return' — the last evaluated expression in the");
    text("  body is the return value. Functions can access module-level variables.");
    section("SYNTAX");
    text("  def name as function(param as type, ...) (");
    text("    // body");
    text("    expression  // return value");
    text("  )");
    section("EXAMPLE");
    text("  def sum as function(a as integer, b as integer) (");
    text("    a + b");
    text("  )");
    println!();
    text("  def n as integer(sum(10, 12))");
    text("  assert(n == 22)");
    println!();
    text("  // match as function body — maps a code to a label");
    text("  def http_label as function(code as integer) (");
    text("    match code (");
    text("      200 => \"200 OK\",");
    text("      201 => \"201 Created\",");
    text("      404 => \"404 Not Found\",");
    text("      _   => \"unknown\"");
    text("    )");
    text("  )");
    println!();
    text("  assert(http_label(200) == \"200 OK\")");
    text("  assert(http_label(999) == \"unknown\")");
    println!();
    text("  // Function accessing a module-level variable");
    text("  def base_url as string(\"https://api.example.com\")");
    println!();
    text("  def get_user as function(id as string) (");
    text("    request(GET)");
    text("      .path(concat(base_url, \"/users/\", id))");
    text("      .do()");
    text("  )");
    println!();
    text("  def res as response(get_user(\"1\"))");
    text("  assert(res.ok())");
}

fn print_headers() {
    text("HEADERS  (alias: hdef)");
    section("DESCRIPTION");
    text("  Set request headers inline using tuples, or load them from a .hdef file.");
    text("  Headers are case-insensitive. If the same header appears more than once,");
    text("  the last value wins — so a .header() call after .headers_from() overrides");
    text("  the file value for that specific header.");
    section(".HDEF FORMAT");
    text("  One 'Name: value' per line. // and # lines are comments.");
    text("  {{variable}} placeholders are substituted via with_var(). Unresolved");
    text("  placeholders cause .do() to abort with a clear error.");
    println!();
    text("  // common request headers");
    text("  Authorization: Bearer {{token}}");
    text("  Accept: application/json");
    text("  X-Client: deflang");
    section("SYNTAX");
    text("  // Inline");
    text("  request(GET)");
    text("    .header(tuple(\"Accept\", \"application/json\"))");
    text("    .header(tuple(\"Authorization\", \"Bearer token\"))");
    text("    .do()");
    println!();
    text("  // From file");
    text("  request(GET)");
    text("    .headers_from(\"hdef/common.hdef\")");
    text("    .with_var(token)");
    text("    .do()");
    println!();
    text("  // Override a file header with an inline value");
    text("  request(GET)");
    text("    .headers_from(\"hdef/common.hdef\")");
    text("    .header(tuple(\"Authorization\", \"Bearer override\"))");
    text("    .do()");
    section("EXAMPLE");
    text("  def token as string(\"my-api-key\")");
    println!();
    text("  def res as response(");
    text("    request(GET)");
    text("      .path(\"https://httpbingo.org/headers\")");
    text("      .header(tuple(\"Accept\", \"application/json\"))");
    text("      .header(tuple(\"Authorization\", concat(\"Bearer \", token)))");
    text("      .do()");
    text("  )");
    println!();
    text("  assert(res.ok())");
    text("  print(\"Authorization: {{res.header(\\\"authorization\\\")}}\")");
}

fn print_imported() {
    text("IMPORTED");
    section("DESCRIPTION");
    text("  Loads another .def file into its own isolated interpreter context and");
    text("  exposes its functions and variables as members of the binding name.");
    text("  The path is resolved relative to the importing file.");
    text("  The .def extension may be omitted.");
    println!();
    text("  Module-level variables can be read and assigned from outside.");
    text("  Functions inside a module have access to the module's own variables.");
    section("SYNTAX");
    text("  def mod as imported(\"path/to/module\")");
    println!();
    text("  mod.function_name(args)");
    text("  mod.variable_name");
    text("  mod.variable_name = new_value");
    section("EXAMPLE");
    text("  // math.def");
    text("  def add as function(a as integer, b as integer) (");
    text("    a + b");
    text("  )");
    text("  def multiplier as integer(2)");
    println!();
    text("  // main.def");
    text("  def math as imported(\"math\")");
    println!();
    text("  assert(math.add(10, 12) == 22)");
    println!();
    text("  math.multiplier = 3");
    text("  assert(math.multiplier == 3)");
    println!();
    text("  // API module pattern");
    text("  def api as imported(\"api\")");
    println!();
    text("  def res as response(api.get_user(\"1\"))");
    text("  assert(res.ok())");
    text("  print(\"{{api.base_url}} responded: {{res.describe_status()}}\")");
}

fn print_integer() {
    text("INTEGER");
    section("DESCRIPTION");
    text("  64-bit signed integer numbers. Arithmetic operators +, -, *, /, % are");
    text("  supported. Division of two integers produces a float.");
    section("SYNTAX");
    text("  def n as integer(10)");
    text("  def n as integer              // defaults to 0");
    text("  def n as const integer(10)    // immutable — reassignment is a runtime error");
    section("OPERATORS");
    text("  +    Addition");
    text("  -    Subtraction");
    text("  *    Multiplication");
    text("  /    Division (always produces float)");
    text("  %    Modulo");
    text("  +=   Add and assign");
    text("  -=   Subtract and assign");
    section("COMPARISON");
    text("  ==   Equal          !=   Not equal");
    text("  >    Greater than   <    Less than");
    text("  >=   Greater or equal   <=   Less or equal");
    section("METHODS");
    text("  random(min, max)   Return a random integer in [min, max] (inclusive)");
    text("  to_string()        Convert the integer to its string representation");
    section("EXAMPLE");
    text("  def a as integer(10)");
    text("  a += 5");
    text("  a -= 3");
    println!();
    text("  def b as integer(a * 2)");
    text("  def c as integer(b % 7)");
    println!();
    text("  print(\"a = {{a}}\")   // 12");
    text("  print(\"b = {{b}}\")   // 24");
    text("  print(\"c = {{c}}\")   // 3");
    println!();
    text("  assert(a == 12)");
    text("  assert(b > a)");
    text("  assert(c >= 0)");
    text("  assert(c <= 6)");
    println!();
    text("  // Random number in a range");
    text("  def roll as integer().random(1, 6)");
    text("  assert(roll >= 1)");
    text("  assert(roll <= 6)");
    println!();
    text("  // Convert to string");
    text("  def n as integer(42)");
    text("  def s as string(n.to_string())");
    text("  assert(s == \"42\")");
}

fn print_match() {
    text("MATCH");
    section("DESCRIPTION");
    text("  Compares a value against literal patterns in order and evaluates the");
    text("  expression of the first matching arm. _ is the catch-all wildcard.");
    text("  match is an expression — it can initialize a variable, serve as a function");
    text("  body, or appear anywhere a value is expected.");
    section("SYNTAX");
    text("  match value (");
    text("    pattern => expression,");
    text("    pattern => expression,");
    text("    _       => expression");
    text("  )");
    section("EXAMPLE");
    text("  def status as integer(201)");
    println!();
    text("  def label as string(");
    text("    match status (");
    text("      200 => \"ok\",");
    text("      201 => \"created\",");
    text("      404 => \"not found\",");
    text("      _   => \"unexpected\"");
    text("    )");
    text("  )");
    println!();
    text("  assert(label == \"created\")");
    println!();
    text("  // match as a function body");
    text("  def describe as function(code as integer) (");
    text("    match code (");
    text("      200 => \"200 OK\",");
    text("      201 => \"201 Created\",");
    text("      404 => \"404 Not Found\",");
    text("      500 => \"500 Internal Server Error\",");
    text("      _   => \"unknown\"");
    text("    )");
    text("  )");
    println!();
    text("  def res as response(request(GET).path(\"https://httpbingo.org/get\").do())");
    text("  print(\"verdict: {{describe(res.status())}}\")");
}

fn print_mock() {
    text("MOCK");
    section("DESCRIPTION");
    text("  Define pre-configured HTTP request/response pairs to intercept matching");
    text("  requests without making real network calls. Mocks are matched by HTTP method");
    text("  (case-insensitive) and exact URL string. Unmatched requests fall through to");
    text("  the real network.");
    text("  In dry-run (check) mode, mocks are skipped and a stub 200 is returned as usual.");
    section("SYNTAX");
    text("  mock(METHOD, url)               Create a mock for the given method and URL");
    println!();
    text("  STATUS & BODY");
    text("    .reply(status)                Configure status code (keep body set by body_from)");
    text("    .reply(status, body)          Configure status code and inline body string");
    text("    .fail(status)                 Alias for .reply() — semantic marker for errors");
    text("    .fail(status, body)           Alias for .reply(status, body)");
    println!();
    text("  RESPONSE HEADERS");
    text("    .header(\"Name\", \"value\")      Add a response header inline");
    text("    .headers_from(\"file.hdef\")    Load response headers from a .hdef file");
    println!();
    text("  BODY FROM FILE");
    text("    .body_from(\"file.jdef\")       Load response body from a .jdef or .tdef file");
    text("    .with_var(identifier)         Register a template variable for file templates");
    println!();
    text("  TIMING");
    text("    .delay(ms)                    Add delay in milliseconds before responding");
    section(".HDEF FORMAT (response headers)");
    text("  One 'Name: value' per line. // and # lines are comments.");
    text("  {{variable}} placeholders are substituted via with_var().");
    println!();
    text("  // response_headers.hdef");
    text("  Content-Type: application/json");
    text("  X-Request-Id: {{request_id}}");
    section(".JDEF / .TDEF FORMAT (response body)");
    text("  Any text file. {{variable}} placeholders are substituted via with_var().");
    println!();
    text("  // user.jdef");
    text("  {\"id\": 1, \"name\": \"{{username}}\", \"email\": \"{{email}}\"}");
    section("WITH_MOCKS");
    text("  Pass mocks to a request using .with_mocks():");
    println!();
    text("  request(GET)");
    text("    .path(url)");
    text("    .with_mocks(array_of_mocks)   // pass an array");
    text("    .with_mocks(single_mock)      // or a single mock value directly");
    text("    .do()");
    section("EXAMPLE");
    text("  // Inline body");
    text("  def users_mock as mock(GET, \"https://api.example.com/users\").reply(");
    text("    200,");
    text("    \"[{\\\"name\\\": \\\"Marcelo\\\"}]\"");
    text("  )");
    println!();
    text("  // Error scenario");
    text("  def error_mock as mock(POST, \"https://api.example.com/users\").fail(");
    text("    409,");
    text("    \"{\\\"error\\\": \\\"user already exists\\\"}\"");
    text("  )");
    println!();
    text("  // Body from file with template variables and custom headers");
    text("  def request_id as string(\"abc-123\")");
    text("  def username   as string(\"Marcelo\")");
    text("  def email      as string(\"marcelo@example.com\")");
    println!();
    text("  def user_mock as mock(GET, \"https://api.example.com/users/1\")");
    text("    .with_var(request_id)");
    text("    .with_var(username)");
    text("    .with_var(email)");
    text("    .headers_from(\"response_headers.hdef\")");
    text("    .body_from(\"user.jdef\")");
    text("    .reply(200)");
    println!();
    text("  // Use all mocks together");
    text("  def mocks as array(users_mock, error_mock, user_mock)");
    println!();
    text("  def res as response(");
    text("    request(GET)");
    text("      .path(\"https://api.example.com/users/1\")");
    text("      .with_mocks(mocks)");
    text("      .do()");
    text("  )");
    println!();
    text("  assert(res.ok())");
    text("  assert(res.body_contains(\"Marcelo\"))");
    text("  assert(res.header(\"Content-Type\") == \"application/json\")");
    section("SEE ALSO");
    text("  def help request   Full request builder method reference");
    text("  def help response  Response inspection methods");
    text("  def help body      Body template files (.jdef / .tdef)");
    text("  def help headers   Header files (.hdef)");
}

fn print_query_string() {
    text("QUERY_STRING  (alias: qdef)");
    section("DESCRIPTION");
    text("  Appends URL query parameters to a request inline using tuples, or loads");
    text("  them from a .qdef file. If the same parameter name appears more than once,");
    text("  the last value wins.");
    section(".QDEF FORMAT");
    text("  One 'name: value' per line. // and # lines are comments.");
    text("  {{variable}} placeholders are substituted via with_var(). Unresolved");
    text("  placeholders cause .do() to abort with a clear error.");
    println!();
    text("  // search parameters");
    text("  search: {{search_term}}");
    text("  page: 1");
    text("  per_page: 20");
    section("SYNTAX");
    text("  // Inline");
    text("  request(GET)");
    text("    .path(url)");
    text("    .query_string(tuple(\"page\", \"1\"))");
    text("    .query_string(tuple(\"search\", \"deflang\"))");
    text("    .do()");
    println!();
    text("  // From file");
    text("  request(GET)");
    text("    .path(url)");
    text("    .query_string_from(\"qdef/params.qdef\")");
    text("    .with_var(search_term)");
    text("    .do()");
    section("EXAMPLE");
    text("  def search_term as string(\"deflang\")");
    println!();
    text("  def res as response(");
    text("    request(GET)");
    text("      .path(\"https://httpbingo.org/anything\")");
    text("      .query_string(tuple(\"search\", search_term))");
    text("      .query_string(tuple(\"page\", \"1\"))");
    text("      .do()");
    text("  )");
    println!();
    text("  assert(res.ok())");
    text("  assert(res.body_contains(\"deflang\"))");
    text("  print(\"status: {{res.describe_status()}}\")");
}

fn print_request() {
    text("REQUEST");
    section("DESCRIPTION");
    text("  Builds and executes an HTTP request using a fluent method chain.");
    text("  Call .do() to send the request — it returns a response value.");
    text("  Supports GET, POST, PUT, PATCH, DELETE, and any other HTTP method.");
    section("BUILDER METHODS");
    text("  .path(url)                       Set the request URL");
    text("  .header(tuple(name, value))      Add or replace a request header");
    text("  .headers_from(path)              Load headers from a .hdef file");
    text("  .query_string(tuple(k, v))       Append a query parameter");
    text("  .query_string_from(path)         Load query params from a .qdef file");
    text("  .body_from(path)                 Load body; infers Content-Type from .jdef/.tdef");
    text("  .type(JSON)                      Override Content-Type: application/json");
    text("  .type(TEXT)                      Override Content-Type: text/plain");
    text("  .with_var(variable)              Register a variable for template substitution");
    text("                                   Accepts string, integer, float, or boolean.");
    text("                                   Unresolved {{placeholders}} abort at .do().");
    text("  .inspect()                       Print request details to stdout; returns self");
    text("  .form_from(path)                 Load form fields from a .fdef file; sets Content-Type: application/x-www-form-urlencoded");
    text("  .snapshot()                      Save response snapshot to snapshots/ (once per URL)");
    text("  .mock_with_snapshot()            Use saved response as mock; save on first run");
    text("  .retries(n)                      Retry up to n times on failure");
    text("  .fixed_backoff(ms)               Wait ms between retries (constant)");
    text("  .linear_backoff(ms)              Wait ms, 2×ms, 3×ms, ... between retries");
    text("  .exponential_backoff(ms)         Wait ms, 2×ms, 4×ms, ... between retries");
    text("  .timeout(ms)                     Max time per attempt in milliseconds");
    text("  .timeout(ms, \"message\")          Max time per attempt; show message on failure");
    text("  .do()                            Send the request, return a response");
    section("EXAMPLE");
    text("  // GET with headers and query string");
    text("  def res as response(");
    text("    request(GET)");
    text("      .path(\"https://httpbingo.org/anything\")");
    text("      .header(tuple(\"Accept\", \"application/json\"))");
    text("      .query_string(tuple(\"page\", \"1\"))");
    text("      .do()");
    text("  )");
    println!();
    text("  assert(res.ok())");
    text("  print(\"{{res.describe_status()}} in {{res.duration()}}ms\")");
    println!();
    text("  // POST with JSON body");
    text("  def title as string(\"My post\")");
    println!();
    text("  def create as response(");
    text("    request(POST)");
    text("      .path(\"https://jsonplaceholder.typicode.com/posts\")");
    text("      .body_from(\"jdef/post.jdef\")");
    text("      .with_var(title)");
    text("      .do()");
    text("  )");
    println!();
    text("  assert(create.status() == 201)");
    text("  print(\"created: {{create.describe_status()}}\")");
}

fn print_response() {
    text("RESPONSE");
    section("DESCRIPTION");
    text("  The value returned by request.do(). Provides access to the HTTP status,");
    text("  headers, and body. A default response (status 0, empty body) is the");
    text("  zero value when declared without an initializer.");
    println!();
    text("  HTTP responses with 4xx or 5xx status codes are returned as normal response");
    text("  values — they do not stop the script. Use res.status() or res.ok() to");
    text("  inspect the result and branch with if/else. Only network failures (connection");
    text("  refused, DNS errors, timeouts) cause a request error that stops the script.");
    section("METHODS");
    text("  status()              HTTP status code as integer");
    text("  ok()                  True when status is 2xx");
    text("  describe_status()     Human-readable label: \"200 OK\", \"404 Not Found\", ...");
    text("  duration()            Round-trip time in milliseconds");
    text("  size()                Response body size in bytes");
    text("  body()                Response body as string");
    text("  body_contains(text)   True when the body contains the given substring");
    text("  body_matches(pattern) True when the body matches the given regex pattern");
    text("  html(selector)        Text content of the first element matching the CSS selector");
    text("  html_all(selector)    Array of text content for all matching elements");
    text("  html_attr(sel, attr)  Attribute value of the first matching element");
    text("  content_type()        Value of the Content-Type response header");
    text("  header(name)          Value of a specific header (case-insensitive)");
    text("  headers()             All headers as an array of tuple(name, value)");
    text("  json(path)            Extract a value from a JSON body by JSONPath-like path");
    text("  json_exists(path)     True when the JSON path exists in the body");
    text("  expect(predicate)     Assert a condition; aborts with a readable error if false");
    text("  inspect()             Print response details to stdout; returns self");
    text("  assert_snapshot()     Assert structure matches saved snapshot; returns self");
    section("EXAMPLE");
    text("  def res as response(");
    text("    request(GET)");
    text("      .path(\"https://httpbingo.org/anything\")");
    text("      .do()");
    text("  )");
    println!();
    text("  assert(res.ok())");
    text("  assert(res.status() == 200)");
    println!();
    text("  print(\"status:       {{res.describe_status()}}\")");
    text("  print(\"duration:     {{res.duration()}}ms\")");
    text("  print(\"size:         {{res.size()}} bytes\")");
    text("  print(\"content-type: {{res.content_type()}}\")");
    println!();
    text("  // Iterate all response headers");
    text("  for h in res.headers() (");
    text("    print(\"{{h.key()}}: {{h.value()}}\")");
    text("  )");
    println!();
    text("  // Read a specific header");
    text("  def ct as string(res.header(\"content-type\"))");
    text("  assert(ct != \"\")");
    println!();
    text("  // Handle HTTP error responses — script does not stop on 4xx/5xx");
    text("  def res as response(");
    text("    request(POST)");
    text("      .path(\"https://api.example.com/cpf\")");
    text("      .do()");
    text("  )");
    println!();
    text("  if res.ok() (");
    text("    print(\"registered successfully\")");
    text("  ) else (");
    text("    def status as integer(res.status())");
    text("    print(\"failed with status: {{status}}\")");
    text("    print(\"body: {{res.body()}}\")");
    text("  )");
}

fn print_retry() {
    text("RETRY");
    section("DESCRIPTION");
    text("  Adds automatic retry and backoff to any request. retries(n) re-sends the");
    text("  request up to n additional times on network failure (connection refused,");
    text("  DNS error, timeout). HTTP error responses (4xx/5xx) are not retried — they");
    text("  are returned as normal response values. A backoff strategy controls the wait");
    text("  between attempts. timeout sets the maximum time allowed per attempt.");
    section("RETRY");
    text("  .retries(n)   Re-send the request up to n times on failure. Default: 0.");
    section("BACKOFF STRATEGIES");
    text("  Only the last backoff method set takes effect.");
    println!();
    text("  .fixed_backoff(ms)");
    text("      Wait ms milliseconds before each retry (constant).");
    text("      Attempt 1: ms   Attempt 2: ms   Attempt 3: ms");
    println!();
    text("  .linear_backoff(ms)");
    text("      Wait increases by ms on each retry.");
    text("      Attempt 1: ms   Attempt 2: 2×ms   Attempt 3: 3×ms");
    println!();
    text("  .exponential_backoff(ms)");
    text("      Wait doubles on each retry.");
    text("      Attempt 1: ms   Attempt 2: 2×ms   Attempt 3: 4×ms");
    section("TIMEOUT");
    text("  .timeout(ms)               Abort the attempt after ms milliseconds.");
    text("  .timeout(ms, \"message\")    Abort after ms ms; show message on network failure.");
    println!();
    text("  The message replaces low-level network error text. Useful for user-facing");
    text("  workflows where 'connection refused' is less readable than a custom string.");
    section("EXAMPLE");
    text("  // 3 retries with exponential backoff, 2-second timeout per attempt");
    text("  def res as response(");
    text("    request(GET)");
    text("      .path(\"https://api.example.com/data\")");
    text("      .retries(3)");
    text("      .exponential_backoff(100)");
    text("      .timeout(2000, \"service did not respond within 2 seconds\")");
    text("      .do()");
    text("  )");
    println!();
    text("  assert(res.ok())");
    println!();
    text("  // Fixed backoff, no timeout message");
    text("  request(POST)");
    text("    .path(\"https://api.example.com/jobs\")");
    text("    .retries(2)");
    text("    .fixed_backoff(500)");
    text("    .do()");
    section("SEE ALSO");
    text("  def help request   Full builder method reference");
}

fn print_string() {
    text("STRING");
    section("DESCRIPTION");
    text("  UTF-8 text values. String concatenation uses the concat() builtin.");
    text("  There are no string arithmetic operators.");
    text("  String literals inside print() support {{expression}} interpolation.");
    section("SYNTAX");
    text("  def name as string(\"Marcelo\")");
    text("  def name as string()              // defaults to empty string \"\"");
    text("  def name as string                // same default");
    text("  def name as const string(\"Def\")  // immutable — reassignment is a runtime error");
    section("MULTILINE STRINGS");
    text("  Use triple-quote delimiters \"\"\"...\"\"\" to write multi-line strings.");
    text("  The leading newline after the opening \"\"\" is stripped automatically.");
    text("  If the closing \"\"\" is indented, that indentation is stripped from every");
    text("  content line (dedent). Inner lines with deeper indentation keep their");
    text("  relative indent.");
    println!();
    text("  def body as string(\"\"\"");
    text("    {");
    text("      \"name\": \"Marcelo\"");
    text("    }");
    text("    \"\"\")");
    println!();
    text("  // Produces the string:");
    text("  //   {");
    text("  //     \"name\": \"Marcelo\"");
    text("  //   }");
    section("METHODS");
    text("  from_env_var(\"VAR\")   Read the value of an environment variable");
    section("BUILTINS");
    text("  concat(a, b, ...)   Join two or more strings");
    section("EXAMPLE");
    text("  def first as string(\"Marcelo\")");
    text("  def last  as string(\"Castellani\")");
    text("  def full  as string(concat(first, \" \", last))");
    println!();
    text("  print(full)                   // Marcelo Castellani");
    text("  print(\"Hello, {{full}}!\")       // Hello, Marcelo Castellani!");
    println!();
    text("  assert(full == \"Marcelo Castellani\")");
    text("  assert(full != \"\")");
    println!();
    text("  // Build a URL from parts");
    text("  def base as string(\"https://api.example.com\")");
    text("  def id   as string(\"42\")");
    text("  def url  as string(concat(base, \"/users/\", id))");
    println!();
    text("  // Reading from the system environment");
    text("  def home as string().from_env_var(\"HOME\")");
    text("  print(\"home: {{home}}\")");
}

fn print_json() {
    text("JSON  (aliases: json_assertions, jsonpath)");
    section("DESCRIPTION");
    text("  response.json(path) extracts a value from a JSON response body using a");
    text("  simple JSONPath-like syntax. response.json_exists(path) returns true when");
    text("  the path exists, false otherwise — no error on missing paths.");
    section("PATH SYNTAX");
    text("  $                   Root of the document");
    text("  $.field             Field access");
    text("  $.parent.child      Nested field access");
    text("  $.array[0]          Array element by index");
    text("  $.array[1].field    Combined array and field access");
    println!();
    text("  Rules:");
    text("  - Path must start with $");
    text("  - Field names: letters, digits, _ and -");
    text("  - Array indices: non-negative integers only");
    text("  - No wildcards, filters, slices, or recursive descent (..)");
    section("RETURN TYPES");
    text("  JSON string   →  string");
    text("  JSON integer  →  integer");
    text("  JSON float    →  float");
    text("  JSON boolean  →  boolean");
    text("  JSON null     →  nil");
    text("  JSON object   →  string (compact JSON representation)");
    text("  JSON array    →  string (compact JSON representation)");
    section("ERRORS");
    text("  response body is not valid JSON");
    text("  invalid json path '...'          Path does not follow the syntax rules");
    text("  json path '...' not found        Path exists check failed in json()");
    text("  json_exists() returns false, not an error, when the path is absent");
    section("SYNTAX");
    text("  res.json(\"path\")         Extract value at path (error if missing)");
    text("  res.json_exists(\"path\")  True when path exists");
    section("EXAMPLE");
    text("  def m as mock(GET, \"https://api.example.com/users/1\")");
    text("    .header(\"Content-Type\", \"application/json\")");
    text("    .reply(200, \"{id:1}\")   // body loaded from file in real use");
    println!();
    text("  def res as response(");
    text("    request(GET)");
    text("      .path(\"https://api.example.com/users/1\")");
    text("      .with_mocks(m)");
    text("      .do()");
    text("  )");
    println!();
    text("  assert(res.json(\"$.id\") == 1)");
    text("  assert(res.json(\"$.active\") == true)");
    text("  assert(res.json(\"$.address.city\") == \"São Paulo\")");
    text("  assert(res.json(\"$.roles[0]\") == \"admin\")");
    println!();
    text("  assert(res.json_exists(\"$.id\"))");
    text("  assert(not res.json_exists(\"$.missing\"))");
    section("SEE ALSO");
    text("  def help response  Full list of response methods");
    text("  def help assert    Global assert() builtin");
    text("  def help mock      Intercept HTTP requests with pre-configured responses");
}

fn print_expect() {
    text("EXPECT");
    section("DESCRIPTION");
    text("  Asserts a condition on a response value using readable field names.");
    text("  If the predicate is false, execution aborts with a descriptive error message");
    text("  that includes the predicate text and the current response values.");
    text("  expect() returns the response, so calls can be chained.");
    println!();
    text("  Available fields inside the predicate:");
    text("    status         HTTP status code (integer)");
    text("    ok             True when status is 2xx (boolean)");
    text("    duration       Round-trip time in milliseconds (integer)");
    text("    size           Response body size in bytes (integer)");
    text("    body           Response body (string)");
    text("    content_type   Value of the Content-Type header (string)");
    section("SYNTAX");
    text("  res.expect(predicate)");
    text("  res.expect(predicate).expect(predicate)   // chainable");
    section("EXAMPLE");
    text("  def res as response(");
    text("    request(GET)");
    text("      .path(\"https://httpbingo.org/anything\")");
    text("      .do()");
    text("  )");
    println!();
    text("  res.expect(ok)");
    text("  res.expect(status == 200)");
    text("  res.expect(duration < 5000)");
    println!();
    text("  // Chainable");
    text("  res.expect(ok).expect(status == 200).expect(duration < 5000)");
    println!();
    text("  // Error message when a predicate fails:");
    text("  // runtime error: expect(status == 201) failed: status=200, ok=true, duration=142ms");
    section("SEE ALSO");
    text("  def help assert    Global assert() builtin");
    text("  def help response  Full list of response methods");
}

fn print_params() {
    text("PARAMS  (alias: from_cmd_param, cmdparam)");
    section("DESCRIPTION");
    text("  Scripts can accept runtime parameters so they can be run against different");
    text("  data without editing the file. Pass values on the command line with");
    text("  --param KEY=VALUE (repeatable). Read them inside the script with the");
    text("  from_cmd_param() built-in function.");
    section("CLI USAGE");
    text("  def run script.def --param cpf=999.000.111-00 --param page=2");
    text("  def check script.def --param cpf=999.000.111-00");
    section("SYNTAX");
    text("  from_cmd_param(\"name\", default)   Read param; use default if not passed");
    text("  from_cmd_param(\"name\")            Read param; abort if not passed");
    println!();
    text("  The default value's type determines how the CLI string is parsed:");
    println!();
    text("  def cpf    as string(from_cmd_param(\"cpf\",    \"000.000.000-00\"))");
    text("  def count  as integer(from_cmd_param(\"count\",  0))");
    text("  def price  as float(from_cmd_param(\"price\",   9.99))");
    text("  def active as boolean(from_cmd_param(\"active\", false))");
    println!();
    text("  For datetime, pass a datetime default:");
    text("  def now as datetime");
    text("  def start as datetime(from_cmd_param(\"start\", now))");
    text("  // --param start=2026-01-15  or  --param start=2026-01-15T10:30:00+00:00");
    section("ERRORS");
    text("  If the param is missing and no default is given:");
    text("    runtime error: required param 'cpf' not provided — pass --param cpf=<value>");
    println!();
    text("  If the value cannot be parsed into the target type:");
    text("    runtime error: cannot parse param 'count' value \"batata\" as integer");
    section("EXAMPLE");
    text("  // create_user.def");
    text("  def cpf   as string(from_cmd_param(\"cpf\",  \"111.222.333-44\"))");
    text("  def name  as string(from_cmd_param(\"name\", \"Test User\"))");
    text("  def page  as integer(from_cmd_param(\"page\",  1))");
    println!();
    text("  def res as response(");
    text("    request(POST)");
    text("      .path(concat(\"https://api.example.com/users/\", cpf))");
    text("      .body_from(\"body/user.jdef\")");
    text("      .with_var(name)");
    text("      .with_var(page)");
    text("      .do()");
    text("  )");
    println!();
    text("  assert(res.ok())");
    println!();
    text("  // Run: def run create_user.def --param cpf=999.000.111-00 --param name=\"João\"");
    section("SEE ALSO");
    text("  def help request   Request builder methods including with_var");
    text("  def help inspect   Debug requests and responses at runtime");
}

fn print_inspect() {
    text("INSPECT");
    section("DESCRIPTION");
    text("  inspect() prints the full details of a request or response to stdout.");
    text("  Both methods return self, so they can be placed inline without breaking");
    text("  a chain. Use them to debug unexpected behaviour without adding extra");
    text("  variables or print statements.");
    section("REQUEST — call before .do()");
    text("  request.inspect() prints:");
    text("    method and URL, headers, query parameters, body, template vars,");
    text("    retry count and backoff strategy, timeout.");
    println!();
    text("  request(POST)");
    text("    .path(url)");
    text("    .header(tuple(\"Authorization\", \"Bearer token\"))");
    text("    .body_from(\"post.jdef\")");
    text("    .with_var(title)");
    text("    .inspect()   // <-- here, before .do()");
    text("    .do()");
    println!();
    text("  Output:");
    text("    [inspect] POST https://api.example.com/posts");
    text("      headers:");
    text("        Authorization: Bearer token");
    text("      body:");
    text("        {\"title\": \"DefLang post\"}");
    text("      vars:");
    text("        title: DefLang post");
    section("RESPONSE — call on a response value");
    text("  response.inspect() prints:");
    text("    status code, ok/error label, duration, headers, body.");
    println!();
    text("  res.inspect()");
    println!();
    text("  Output:");
    text("    [inspect] 201 (ok, 142ms)");
    text("      headers:");
    text("        content-type: application/json");
    text("      body:");
    text("        {\"id\": 101}");
    println!();
    text("  Because inspect() returns the response, it can be chained:");
    text("  res.inspect().expect(ok).expect(status == 201)");
    section("EXAMPLE");
    text("  def res as response(");
    text("    request(GET)");
    text("      .path(\"https://jsonplaceholder.typicode.com/posts/1\")");
    text("      .inspect()");
    text("      .do()");
    text("  )");
    println!();
    text("  res.inspect()");
    println!();
    text("  assert(res.ok())");
    section("SEE ALSO");
    text("  def help request   Full list of request builder methods");
    text("  def help response  Full list of response methods");
    text("  def help expect    Assert response conditions with readable error messages");
}

fn print_snapshot() {
    text("SNAPSHOT");
    section("DESCRIPTION");
    text("  .snapshot() records the HTTP response (status, headers, body)");
    text("  as files inside a snapshots/ directory next to the running .def script.");
    text("  The snapshot is only written once — if a snapshot for the same method");
    text("  and URL already exists, the step is silently skipped.");
    section("SYNTAX");
    text("  request(METHOD)");
    text("    .path(url)");
    text("    ...");
    text("    .snapshot()");
    text("    .do()");
    section("FILES");
    text("  snapshots/{name}.sdef   Response status code");
    text("  snapshots/{name}.hdef   Response headers");
    text("  snapshots/{name}.jdef   Response body (Content-Type: application/json)");
    text("  snapshots/{name}.tdef   Response body (Content-Type: text/plain)");
    println!();
    text("  Only files that have content are written. {name} is derived from the");
    text("  HTTP method and URL plus a Unix timestamp, e.g.:");
    text("    post-api-example-com-users-1748789600");
    section("EXAMPLE");
    text("  def res as response(");
    text("    request(POST)");
    text("      .path(\"https://api.example.com/users\")");
    text("      .body_from(\"jdef/user.jdef\")");
    text("      .snapshot()");
    text("      .do()");
    text("  )");
    println!();
    text("  After the first run you will find:");
    text("    snapshots/post-api-example-com-users-1748789600.sdef   (e.g. \"201\")");
    text("    snapshots/post-api-example-com-users-1748789600.hdef");
    text("    snapshots/post-api-example-com-users-1748789600.jdef");
    println!();
    text("  Subsequent runs skip the snapshot because the slug already exists.");
    section("NOTES");
    text("  .snapshot() has no effect in dry-run mode (def check).");
    text("  Place it anywhere in the builder chain before .do().");
    section("MOCK WITH SNAPSHOT");
    text("  .mock_with_snapshot() uses the same snapshot files as .snapshot():");
    text("  - First run: executes the real HTTP call and saves the response.");
    text("  - Subsequent runs: loads the saved response and returns it without");
    text("    making any network call.");
    println!();
    text("  The files are identical in format and naming to .snapshot() — they are");
    text("  fully interchangeable. Running .snapshot() on an endpoint and then");
    text("  switching to .mock_with_snapshot() will replay the existing snapshot.");
    println!();
    text("  EXAMPLE");
    text("    def res as response(");
    text("      request(POST)");
    text("        .path(\"https://api.example.com/users\")");
    text("        .body_from(\"jdef/user.jdef\")");
    text("        .mock_with_snapshot()");
    text("        .do()");
    text("    )");
    println!();
    text("  On the first run, the real POST is sent and the response is stored at:");
    text("    snapshots/post-api-example-com-users-{timestamp}.sdef");
    text("    snapshots/post-api-example-com-users-{timestamp}.hdef");
    text("    snapshots/post-api-example-com-users-{timestamp}.jdef");
    println!();
    text("  On subsequent runs, the response is replayed instantly from those files.");
    section("ASSERT SNAPSHOT");
    text("  assert_snapshot() validates that the current response matches the structure");
    text("  of the saved snapshot. It is called on the response value after .do().");
    text("  The snapshot must already exist (created by .snapshot() or .mock_with_snapshot()).");
    println!();
    text("  What is validated:");
    text("    - Status code must match the snapshot exactly.");
    text("    - JSON body: field names and types are compared recursively.");
    text("      Values may change; types cannot.");
    text("    - Text body: presence/absence must match.");
    println!();
    text("  EXAMPLE");
    text("    def res as response(");
    text("      request(GET)");
    text("        .path(\"https://api.example.com/users/1\")");
    text("        .snapshot()");
    text("        .do()");
    text("    )");
    println!();
    text("    res.assert_snapshot()");
    println!();
    text("  On type mismatch, the error names the exact path:");
    text("    assert_snapshot failed: JSON structure changed");
    text("    $.user.id: expected number, got string");
    println!();
    text("  assert_snapshot() returns self, so it can be chained:");
    text("    res.assert_snapshot().expect(ok)");
    section("SEE ALSO");
    text("  def help request   Full list of request builder methods");
    text("  def help inspect   Debug request and response details");
}

fn print_const() {
    text("CONST");
    section("DESCRIPTION");
    text("  The const modifier makes a variable immutable after definition.");
    text("  Any attempt to reassign it — including compound operators (+=, -=)");
    text("  and mutation methods (push, datetime setters) — stops the script");
    text("  with a clear runtime error.");
    println!();
    text("  const applies to all basic types: integer, float, string, boolean,");
    text("  array, tuple, and datetime.");
    section("SYNTAX");
    text("  def name as const <type>(<value>)");
    println!();
    text("  def max_retries  as const integer(3)");
    text("  def pi           as const float(3.14159)");
    text("  def app_name     as const string(\"DefLang\")");
    text("  def debug_mode   as const boolean(false)");
    text("  def allowed      as const array(\"GET\", \"POST\")");
    text("  def content_type as const tuple(\"Content-Type\", \"application/json\")");
    text("  def frozen_date  as const datetime");
    section("SCOPE");
    text("  const is scoped. An outer const can be shadowed by a mutable inner");
    text("  binding, and vice versa — they are independent bindings.");
    println!();
    text("  def x as const integer(1)");
    text("  if true (");
    text("    def x as integer(99)   // new mutable binding in this scope");
    text("    x = 50                 // ok — modifies the inner x");
    text("  )");
    text("  // x = 2  // would fail — outer x is still const");
    section("ERRORS");
    text("  cannot assign to const variable 'x'");
    text("  cannot call push() on const array 'items'");
    text("  cannot call 'day()' on const datetime 'epoch'");
    section("SEE ALSO");
    text("  def help integer   integer type and operators");
    text("  def help float     float type and operators");
    text("  def help string    string type and methods");
    text("  def help boolean   boolean type and operators");
    text("  def help array     array type and methods");
    text("  def help tuple     tuple type and methods");
    text("  def help datetime  datetime type and methods");
}

fn print_tuple() {
    text("TUPLE");
    section("DESCRIPTION");
    text("  A key/value pair. The key must be a string; the value may be string,");
    text("  integer, float, or boolean. Tuples are the primary way to pass headers");
    text("  and query parameters to requests. Response headers are returned as tuples.");
    section("SYNTAX");
    text("  tuple(\"key\", value)");
    text("  def t as const tuple(\"key\", value)  // immutable — reassignment is a runtime error");
    section("METHODS");
    text("  key()     The key string");
    text("  value()   The associated value");
    section("EXAMPLE");
    text("  def age as tuple(\"Age\", 48)");
    println!();
    text("  assert(age.key()   == \"Age\")");
    text("  assert(age.value() == 48)");
    println!();
    text("  // Tuples as request headers");
    text("  request(GET)");
    text("    .header(tuple(\"Accept\", \"application/json\"))");
    text("    .header(tuple(\"Authorization\", \"Bearer token\"))");
    text("    .do()");
    println!();
    text("  // Tuples as query parameters");
    text("  request(GET)");
    text("    .query_string(tuple(\"page\", \"1\"))");
    text("    .query_string(tuple(\"per_page\", \"20\"))");
    text("    .do()");
    println!();
    text("  // Response headers are an array of tuples");
    text("  def res as response(request(GET).path(\"https://httpbingo.org/get\").do())");
    println!();
    text("  for h in res.headers() (");
    text("    print(\"{{h.key()}}: {{h.value()}}\")");
    text("  )");
}

fn print_form() {
    text("FORM");
    section("DESCRIPTION");
    text("  .form_from(path) loads HTML form fields from a .fdef file and encodes them");
    text("  as application/x-www-form-urlencoded in the request body.");
    text("  Field values are URL-encoded automatically. Template variable substitution");
    text("  is supported via with_var(...).");
    section("SYNTAX");
    text("  request(POST)");
    text("    .path(url)");
    text("    .form_from(\"path/to/form.fdef\")");
    text("    .with_var(variable)");
    text("    .do()");
    section("FILE FORMAT (.fdef)");
    text("  One field per line: key: value");
    text("  Lines starting with // or # are comments.");
    text("  {{variable}} placeholders are substituted via with_var(...).");
    println!();
    text("  // login.fdef");
    text("  username: {{username}}");
    text("  password: {{password}}");
    text("  remember_me: true");
    section("EXAMPLE");
    text("  def username as string(\"alice\")");
    text("  def password as string(\"s3cr3t\")");
    println!();
    text("  def res as response(");
    text("    request(POST)");
    text("      .path(\"https://example.com/login\")");
    text("      .form_from(\"login.fdef\")");
    text("      .with_var(username)");
    text("      .with_var(password)");
    text("      .do()");
    text("  )");
    println!();
    text("  assert(res.ok())");
    section("SEE ALSO");
    text("  def help request   Full list of request builder methods");
    text("  def help body      .jdef and .tdef body file formats");
}

fn print_html() {
    text("HTML");
    section("DESCRIPTION");
    text("  Three response methods support querying HTML bodies using CSS selectors.");
    text("  body_matches(pattern) tests the body against a regular expression.");
    section("METHODS");
    text("  body_matches(pattern)");
    text("      True when the response body matches the given regex pattern.");
    text("      Works on any body type — HTML, JSON, or plain text.");
    println!();
    text("  html(selector)");
    text("      Returns the trimmed text content of the first element matching the");
    text("      CSS selector. Returns an empty string when nothing matches.");
    println!();
    text("  html_all(selector)");
    text("      Returns an array of trimmed text content for all matching elements.");
    text("      Declare the target variable as array() and assign:");
    text("        def items as array()");
    text("        items = res.html_all(\"li\")");
    println!();
    text("  html_attr(selector, attribute)");
    text("      Returns the attribute value of the first matching element.");
    text("      Returns an empty string when the element or attribute is not found.");
    section("EXAMPLE");
    text("  def res as response(");
    text("    request(GET)");
    text("      .path(\"https://example.com/\")");
    text("      .do()");
    text("  )");
    println!();
    text("  // Regex match");
    text("  assert(res.body_matches(\"Welcome\"))");
    text("  assert(res.body_matches(\"<title>.+</title>\"))");
    println!();
    text("  // First matching element text");
    text("  def title as string(res.html(\"title\"))");
    text("  def hero  as string(res.html(\"h1.hero\"))");
    println!();
    text("  // All matching elements");
    text("  def links as array()");
    text("  links = res.html_all(\"nav a\")");
    println!();
    text("  // Attribute of first match");
    text("  def url as string(res.html_attr(\"a.source\", \"href\"))");
    section("SEE ALSO");
    text("  def help response   All response methods");
    text("  def help json       JSON path assertions");
}
