## Formula Syntax Reference

CRITICAL: This application uses a CUSTOM formula language. It is NOT JavaScript. You must follow the syntax rules below exactly.

### Variables
Variables are bare identifiers. There is NO var, let, or const keyword.
  CORRECT: myVar = 10
  WRONG:   var myVar = 10

You can optionally annotate types (decorative only):
  myVar:number = 10

### Arithmetic Operations
Standard arithmetic operators: +, -, *, /
Division by zero returns 0 (not an error).
  Example: [Field 1] * [Field 2] + 10
  Example: (10 + 10) * 2 / 5

String concatenation also uses the + operator:
  Example: "Hello " + [Field 1].GetText() + "!"

IMPORTANT: There is NO ++ or -- operator. Use i = i + 1 instead.

### Field References
- Reference fields using the format: [Field X] where X is the field ID number
- Example: [Field 1], [Field 2], [Field 15]
- Fixed fields use the format: [RNFX "Id" RNFX] for system values (see Fixed Fields section below)

### Fixed Fields (System Values)
These are built-in system values you can use in formulas. Reference them with [RNFX "Id" RNFX]:
- [RNFX "CurrentDate" RNFX] — The current date. Useful for date comparisons and calculations
- [RNFX "FullUserName" RNFX] — The full name of the logged-in WordPress user
- [RNFX "FirstName" RNFX] — The first name of the logged-in WordPress user
- [RNFX "LastName" RNFX] — The last name of the logged-in WordPress user
- [RNFX "Email" RNFX] — The email of the logged-in WordPress user
- [RNFX "UserRole" RNFX] — The role of the logged-in WordPress user (e.g. "administrator", "subscriber")

Example: RNAddDays([RNFX "CurrentDate" RNFX], 30) returns the date 30 days from today

### Methods (Dot Notation)
Fields have methods you can call using dot notation: [Field X].MethodName
Parentheses are optional for methods with no arguments.
  Example: [Field 5].GetTotal(10)
  Example: [Field 3].GetText()
  Example: [Field 3].GetText         (same as above, parentheses optional)
  Example: [Field 1].IsReadonly

Optional chaining is supported:
  Example: [Field 1]?.GetText()

### Comparison & Logical Operators
- Comparison: ==, !=, >, >=, <, <=
- Logical AND: &&
- Logical OR: ||
- Logical NOT: ! (unary)

### Conditionals

if/else statements:
  if([Field 1] > 10) [Field 1] * 2 else [Field 1]

  if([Field 1] > 100) {
    discount = [Field 1] * 0.1
    return [Field 1] - discount
  } else {
    return [Field 1]
  }

Ternary expressions:
  [Field 1] > 10 ? "big" : "small"

### Blocks { }
Use curly braces to group multiple statements. The last expression in a block is its return value.
  {1 2 3}          — returns 3
  {1 return 2 3}   — returns 2 (early return)

### Return & Break
- return VALUE — exits the entire formula early with the given value
- break — exits the current for/foreach loop

Without return, the last expression evaluated is the formula result.

### For Loop
Syntax: for(init; condition; increment) statement

IMPORTANT: No var keyword. No ++ operator. Use i = i + 1.

  total = 0
  for(i = 0; i < 10; i = i + 1) total = total + i
  total

With a block:
  total = 0
  for(i = 1; i < 10; i = i + 1) {
    total = total + i
    if(total > 20) break
  }
  total

### ForEach Loop
Syntax: foreach(expression as value) statement
Syntax: foreach(expression as key => value) statement

  total = 0
  foreach([1, 2, 3] as value) total = total + value
  total

With block, return, and break:
  foreach([1, 2, 3] as value) {
    if(value == 2) return "found it"
  }

  foreach([1, 2, 3] as value) {
    if(value == 2) break
  }

### Arrays
Array literals use square brackets:
  [1, 2, 3]

Index access (0-based):
  [1, 2, 3][0]      — returns 1
  myArray[2]         — returns third element

Index assignment:
  a = [1, 2, 3]
  a[1] = "changed"

### Literal Values
- Numbers: 10, 3.14, 0
- Strings: "hello", "world" (double quotes only)
- Booleans: true, false
- Null: null

### Built-in Functions

#### Math Functions
- RNRound(numberToRound, numberOfDecimals): Round a number to the specified decimal places
  Example: RNRound([Field 1] * 1.075, 2) rounds the result to 2 decimals
- RNCeil(numberToRound): Round a number up to the next larger integer
  Example: RNCeil(4.2) returns 5
- RNFloor(numberToRound): Round a number down to the next smaller integer
  Example: RNFloor(4.8) returns 4

#### Date Functions
- RNDateDiff(date1, date2, false): Get the number of days between two dates
  Example: RNDateDiff([Field 3], [Field 4], false) returns how many days between those dates
- RNDatePart(date, part): Get a section of a date. Use "Y" for year, "M" for month, "D" for day, "H" for hour, "S" for second
  Example: RNDatePart([Field 5], "M") returns the month number
- RNAddDays(date, numberOfDays): Return a date plus (or minus) X days
  Example: RNAddDays([Field 11], 7) returns the date 7 days after Field 11

#### Advanced Functions
- RNGetParameter("ParameterName"): Get a query parameter value from the current page URL
  Example: RNGetParameter("coupon") returns the value of ?coupon=... in the URL

#### Formatting Functions
- RNFormatCurrency(numberToFormat): Format a number as currency using the form's configured currency settings
  Example: RNFormatCurrency([Field 1] * 0.9) formats the discounted price as currency
- RNFormatTime(numberInMinutes): Format a number (in minutes) as time (e.g. 70 becomes "1:10")
  Example: RNFormatTime([Field 2]) converts minutes to a time string

### Repeater Fields
A repeater is a field container that allows users to add multiple rows of the same set of fields.
Fields inside a repeater have a "RepeaterParent" property in the field list indicating their parent repeater ID.
When working with repeater child fields, use the parent repeater's methods to aggregate values across all rows:
- `[Field REPEATER_ID].GetNumericalTotal(CHILD_FIELD_ID)` — Returns the sum of numeric values (GetNumber) of a child field across ALL repeater rows. Use this for summing quantities, counts, or any entered numeric values.
- `[Field REPEATER_ID].GetTotal(CHILD_FIELD_ID)` — Returns the sum of prices (GetPrice) of a child field across ALL repeater rows. Use this only when summing prices.
- `[Field REPEATER_ID].GetCount()` — Returns the number of repeater items (rows)
- `[Field REPEATER_ID].GetField(CHILD_FIELD_ID, ROW_INDEX)` — Returns a specific child field at a given row index

IMPORTANT: When the user asks about "sum", "total", or "combined value" of a field, prefer GetNumericalTotal unless they specifically mention prices.

### PROHIBITED Syntax
NEVER use any of the following. They will cause parse errors:
- var, let, const — just assign directly: x = 10
- i++, i-- — use i = i + 1 or i = i - 1
- .length — not available; use for loops with null checks or field methods
- .push(), .pop(), .splice() — not available; build values with concatenation
- .join(), .split(), .map(), .filter(), .reduce() — not available; use for/foreach loops
- .indexOf(), .includes(), .toString() — not available
- function keyword, arrow functions (=>) — not available
- while loops — use for loops instead
- switch/case — use if/else chains
- try/catch/throw — not available
- new, class, this — not available
- typeof, instanceof — not available
- console.log, alert — not available
- Template literals (backticks) — use string concatenation with +
- Single-quoted strings — use double quotes only: "text"
- Semicolons as statement separators — statements are separated by whitespace/newlines

## Common Formula Patterns

### Concatenate text from all rows of a Repeater field
To collect text from a child field across all repeater rows into a comma-separated string:
  result = ""
  for(i = 0; i < 100; i = i + 1) {
    child = [Field REPEATER_ID].GetField(CHILD_FIELD_ID, i)
    if(child == null) break
    if(i > 0) result = result + ", "
    result = result + child.GetText()
  }
  result

### Sum numeric values of a child field across all Repeater rows
Use GetNumericalTotal for numeric values, GetTotal for prices:
  [Field REPEATER_ID].GetNumericalTotal(CHILD_FIELD_ID)
  [Field REPEATER_ID].GetTotal(CHILD_FIELD_ID)

### Conditional pricing
  if([Field 1].GetNumber() > 100) {
    return [Field 1].GetNumber() * 0.9
  } else {
    return [Field 1].GetNumber()
  }

### Count selected options
  [Field 1].GetValue().length is NOT valid. Instead, use:
  count = 0
  foreach([Field 1].GetSelectedOptions() as option) count = count + 1
  count
