// ── .improv file grammar (v2025-04-09) ───────────────────────────────────────
//
// Line-oriented, markdown-flavoured format for multi-dimensional models.
// Sections may appear in any order.
//
// Names: bare alphanumeric or pipe-quoted |like this|.
//   Inside pipes, backslash escapes: \| for literal pipe, \\ for backslash,
//   \n for newline.
// Values: pipe-quoted |text| or bare numbers.

file = {
    SOI ~
    version_line ~
    model_name ~
    initial_view? ~
    section* ~
    EOI
}

version_line  = { "v2025-04-09" ~ NEWLINE ~ blank_lines }
model_name    = { "# " ~ rest_of_line ~ NEWLINE ~ blank_lines }
initial_view  = { "Initial View: " ~ rest_of_line ~ NEWLINE ~ blank_lines }

section = _{
    category_section
  | formulas_section
  | data_section
  | view_section
}

// ── Category ─────────────────────────────────────────────────────────────────

category_section = {
    "## Category: " ~ rest_of_line ~ NEWLINE ~ blank_lines ~
    category_entry*
}

category_entry = _{ group_hierarchy | grouped_item | item_list }

// Comma-separated bare items (no group): `- Food, Gas, Total`
item_list = {
    "- " ~ name ~ ("," ~ " "* ~ name)* ~ NEWLINE ~ blank_lines
}

// Single item with group bracket: `- Jan[Q1]`
grouped_item = {
    "- " ~ name ~ "[" ~ name ~ "]" ~ NEWLINE ~ blank_lines
}

group_hierarchy = {
    "> " ~ name ~ "[" ~ name ~ "]" ~ NEWLINE ~ blank_lines
}

// ── Formulas ─────────────────────────────────────────────────────────────────

formulas_section = {
    "## Formulas" ~ NEWLINE ~ blank_lines ~
    formula_line*
}

formula_line = {
    "- " ~ rest_of_line ~ NEWLINE ~ blank_lines
}

// ── Data ─────────────────────────────────────────────────────────────────────

data_section = {
    "## Data" ~ NEWLINE ~ blank_lines ~
    data_line*
}

data_line = {
    coord_list ~ " = " ~ cell_value ~ NEWLINE ~ blank_lines
}

coord_list = { coord ~ (", " ~ coord)* }
coord      = { name ~ "=" ~ name }

cell_value = _{ number | pipe_quoted | bare_value }

number = @{
    "-"? ~ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? ~ (("e" | "E") ~ ("+" | "-")? ~ ASCII_DIGIT+)?
}

bare_value = @{ (!NEWLINE ~ ANY)+ }

// ── View ─────────────────────────────────────────────────────────────────────

view_section = {
    "## View: " ~ rest_of_line ~ NEWLINE ~ blank_lines ~
    view_entry*
}

view_entry = _{ format_line | hidden_line | collapsed_line | axis_line }

axis_line = {
    name ~ ": " ~ axis_kind ~ (", " ~ name)? ~ NEWLINE ~ blank_lines
}

axis_kind      = @{ "row" | "column" | "page" | "none" }

format_line    = { "format: " ~ rest_of_line ~ NEWLINE ~ blank_lines }
hidden_line    = { "hidden: " ~ name ~ "/" ~ name ~ NEWLINE ~ blank_lines }
collapsed_line = { "collapsed: " ~ name ~ "/" ~ name ~ NEWLINE ~ blank_lines }

// ── Names ────────────────────────────────────────────────────────────────────
//
// A name is either pipe-quoted or a bare identifier.
// Pipe-quoted: |Income, Gross| — backslash escapes inside:
//   \| = literal pipe, \\ = literal backslash, \n = newline
// Bare: no = , | [ ] / : # or newlines.

name = _{ pipe_quoted | bare_name }

pipe_quoted = { "|" ~ pipe_inner ~ "|" }
pipe_inner  = @{ ("\\" ~ ANY | !"|" ~ ANY)* }

bare_name   = @{ ('A'..'Z' | 'a'..'z' | "_") ~ ('A'..'Z' | 'a'..'z' | '0'..'9' | "_" | "-")* }

// ── Shared ───────────────────────────────────────────────────────────────────

rest_of_line = @{ (!NEWLINE ~ ANY)* }
blank_lines  = _{ NEWLINE* }
