// fspulse_query.pest
// interactive parser: https://pest.rs/#editor

// Top-level query: one of the domain-specific queries

query = { SOI ~ WS* ~ query_type ~ where_clause? ~ group_clause? ~ show_clause? ~ order_clause? ~ limit_clause? ~ offset_clause? ~ WS* ~ EOI }

query_type = { "roots" | "scans" | "items" | "versions" | "hashes" }

// Optional WHERE clause
where_clause = _{ WS+ ~ ("WHERE" | "where") ~ WS+ ~ filter_list }
filter_list  = _{ filter ~ (WS* ~ COMMA ~ WS* ~ filter)* }
filter       = _{ id_filter | date_filter | bool_filter | string_filter | hash_filter | path_filter | val_state_filter | hash_state_filter | item_type_filter | scan_state_filter | access_filter | int_filter }

// Optional GROUP BY clause
group_clause = _{ WS+ ~ ("GROUP" | "group") ~ WS+ ~ ("BY" | "by") ~ WS+ ~ group_list }
group_list   =  { group_col ~ (WS* ~ COMMA ~ WS* ~ group_col)* }
group_col    =  { id_col | date_col | bool_col | string_col | hash_col | path_col | val_state_col | hash_state_col | item_type_col | scan_state_col | access_col | int_col }

// Optional SHOW clause
show_clause = _{ WS+ ~ ("SHOW" | "show") ~ WS+ ~ show_list }
show_list   =  { show_spec ~ (WS* ~ COMMA ~ WS* ~ show_spec)* }
show_spec   = _{ default | all | agg_show | id_show | date_show | bool_show | string_show | hash_show | path_show | val_state_show | hash_state_show | item_type_show | scan_state_show | access_show | int_show }

// Aggregate expressions in SHOW and ORDER BY
agg_show  =  { agg_func ~ WS* ~ LPAREN ~ WS* ~ agg_arg ~ WS* ~ RPAREN }
agg_func  =  { ^"count" | ^"sum" | ^"avg" | ^"min" | ^"max" }
agg_arg   =  { "*" | id_col | date_col | bool_col | string_col | hash_col | path_col | val_state_col | hash_state_col | item_type_col | scan_state_col | access_col | int_col }

// Optional ORDER clause
order_clause = _{ WS+ ~ ("ORDER" | "order") ~ WS+ ~ ("BY" | "by") ~ WS+ ~ order_list }
order_list   =  { order_spec ~ (WS* ~ COMMA ~ WS* ~ order_spec)* }
order_spec   =  { order_column ~ (WS+ ~ order_dir)? }
order_column =  { agg_order | id_col | date_col | bool_col | string_col | hash_col | path_col | val_state_col | hash_state_col | item_type_col | scan_state_col | access_col | int_col }
agg_order    =  { agg_func ~ WS* ~ LPAREN ~ WS* ~ agg_arg ~ WS* ~ RPAREN }
order_dir    =  { "ASCENDING" | "ascending" | "ASC" | "asc" | "DESCENDING" | "descending" | "DESC" | "desc" }

// Optional LIMIT clause
limit_clause = _{ WS+ ~ limit_text ~ WS+ ~ limit_val }
limit_text   = _{ ^"limit" }
limit_val    =  { ASCII_DIGIT+ }

// Optional OFFSET clause
offset_clause = _{ WS+ ~ offset_text ~ WS+ ~ offset_val }
offset_text   = _{ ^"offset" }
offset_val    =  { ASCII_DIGIT+ }

// Filter verifiers used discretely within code
id_filter_EOI           = { WS* ~ id_filter_values ~ WS* ~ EOI }
date_filter_EOI         = { WS* ~ date_filter_values ~ WS* ~ EOI }
bool_filter_EOI         = { bool_filter_values ~ WS* ~ EOI }
string_filter_EOI       = { string_filter_values ~ WS* ~ EOI }
hash_filter_EOI         = { hash_filter_values ~ WS* ~ EOI }
path_filter_EOI         = { WS* ~ path_filter_values ~ WS* ~ EOI }
val_state_filter_EOI    = { WS* ~ val_state_filter_values ~ WS* ~ EOI }
hash_state_filter_EOI   = { WS* ~ hash_state_filter_values ~ WS* ~ EOI }
item_type_filter_EOI    = { WS* ~ item_type_filter_values ~ WS* ~ EOI }
scan_state_filter_EOI   = { WS* ~ scan_state_filter_values ~ WS* ~ EOI }
access_filter_EOI       = { WS* ~ access_filter_values ~ WS* ~ EOI }
int_filter_EOI          = { WS* ~ int_comparator ~ WS* ~ EOI }

// Identity Columns
id_col = { "root_id" | "scan_id" | "item_id" | "item_version" | "first_scan_id" | "last_scan_id" | "last_hash_scan" | "last_val_scan" | "prev_hash_scan" | "schedule_id" }

id_show = { id_col }

id_filter        =  { id_col ~ id_filter_body }
id_filter_body   = _{ WS* ~ COLON ~ WS* ~ LPAREN ~ WS* ~ id_filter_values ~ WS* ~ RPAREN }
id_filter_values = _{ id_spec ~ (WS* ~ COMMA ~ WS* ~ id_spec)* }
id_spec          = _{ null | not_null | id_range | id }
id_range         =  { id ~ WS* ~ DOTDOT ~ WS* ~ id }
id               =  { WS* ~ ASCII_DIGIT+ }

// Date Columns
date_col = { "started_at" | "ended_at" | "mod_date" | "created_at" | "updated_at" }

date_show             =  { date_col ~ date_display_modifier? }
date_display_modifier = _{ WS* ~ "@" ~ date_display_mode }
date_display_mode     =  { full | short | timestamp }

date_filter        =  { date_col ~ date_filter_body }
date_filter_body   = _{ WS* ~ COLON ~ WS* ~ LPAREN ~ WS* ~ date_filter_values ~ WS* ~ RPAREN }
date_filter_values = _{ date_spec ~ (WS* ~ COMMA ~ date_spec)* }
date_spec          = _{ null | not_null | date_range | date }
date_range         =  { date ~ WS* ~ DOTDOT ~ WS* ~ date }
date               = _{ date_timestamp | date_long | date_short }
date_timestamp     =  { WS* ~ ASCII_DIGIT{10,} }
date_long          =  { WS* ~ ASCII_DIGIT{4} ~ "-" ~ ASCII_DIGIT{2} ~ "-" ~ ASCII_DIGIT{2} ~ " " ~ ASCII_DIGIT{2} ~ ":" ~ ASCII_DIGIT{2} ~ ":" ~ ASCII_DIGIT{2} }
date_short         =  { WS* ~ ASCII_DIGIT{4} ~ "-" ~ ASCII_DIGIT{2} ~ "-" ~ ASCII_DIGIT{2} }

// Bool Columns
bool_col = { "is_deleted" | "is_added" | "is_current" | "is_hash" | "hash_all" | "is_val" | "was_restarted" | "has_validator" | "do_not_validate" }

bool_show             =  { bool_col ~ bool_display_modifier? }
bool_display_modifier = _{ WS* ~ "@" ~ WS* ~ bool_display_mode }
bool_display_mode     =  { full | short }

bool_filter        =  { bool_col ~ bool_filter_body }
bool_filter_body   = _{ WS* ~ COLON ~ WS* ~ LPAREN ~ WS* ~ bool_filter_values ~ WS* ~ RPAREN }
bool_filter_values = _{ bool ~ (WS* ~ COMMA ~ WS* ~ bool)* }
bool               = _{ null | not_null | bool_true | bool_false }
bool_true          =  { "TRUE" | "true" | "True" | ^"T" }
bool_false         =  { "FALSE" | "false" | "False" | ^"F" }

string_col = { "error" | "val_error" | "file_extension" }

string_show = { string_col }

string_filter        =  { string_col ~ string_filter_body }
string_filter_body   = _{ WS* ~ COLON ~ WS* ~ LPAREN ~ WS* ~ string_filter_values ~ WS* ~ RPAREN }
string_filter_values = _{ WS* ~ string_spec ~ (WS* ~ COMMA ~ WS* ~ string_spec)* }
string_spec          = _{ null | not_null | ("'" ~ string ~ "'") }
string               =  { (("\\'" | (!"'" ~ ANY)))* }

// Hash Columns (stored as BLOB, queried via HEX conversion)
hash_col = { "file_hash" | "hash_old" | "hash_new" }

hash_show = { hash_col }

hash_filter        =  { hash_col ~ hash_filter_body }
hash_filter_body   = _{ WS* ~ COLON ~ WS* ~ LPAREN ~ WS* ~ hash_filter_values ~ WS* ~ RPAREN }
hash_filter_values = _{ WS* ~ hash_spec ~ (WS* ~ COMMA ~ WS* ~ hash_spec)* }
hash_spec          = _{ null | not_null | ("'" ~ hash_string ~ "'") }
hash_string        =  { (("\\'" | (!"'" ~ ANY)))* }

// Path Columns
path_col = { "item_path" | "item_name" | "root_path" }

path_show             =  { path_col ~ path_display_modifier? }
path_display_modifier = _{ WS* ~ "@" ~ path_display_mode }
path_display_mode     =  { full | relative | short | name }

path_filter        =  { path_col ~ path_filter_body }
path_filter_body   = _{ WS* ~ COLON ~ WS* ~ LPAREN ~ WS* ~ path_filter_values ~ WS* ~ RPAREN }
path_filter_values = _{ WS* ~ path_spec ~ (WS* ~ COMMA ~ WS* ~ path_spec)* }
path_spec          = _{ "'" ~ path ~ "'" }
path               =  { (("\\'" | (!"'" ~ ANY)))* }

// Val State Columns
val_state_col = { "val_state" }

val_state_show             =  { val_state_col ~ val_state_display_modifier? }
val_state_display_modifier = _{ WS* ~ "@" ~ WS* ~ val_state_display_mode }
val_state_display_mode     =  { full | short }

val_state_filter        =  { val_state_col ~ val_state_filter_body }
val_state_filter_body   = _{ WS* ~ COLON ~ WS* ~ LPAREN ~ WS* ~ val_state_filter_values ~ WS* ~ RPAREN }
val_state_filter_values = _{ val_state_spec ~ (WS* ~ COMMA ~ WS* ~ val_state_spec)* }
val_state_spec          = _{ null | not_null | val_state }
val_state               =  {
    "UNKNOWN"
  | "unknown"
  | "Unknown"
  | "VALID"
  | "valid"
  | "Valid"
  | "INVALID"
  | "invalid"
  | "Invalid"
  | "NO VALIDATOR"
  | "no validator"
  | "No Validator"
  | "NOVALIDATOR"
  | "novalidator"
  | "NoValidator"
  | ^"U"
  | ^"V"
  | ^"I"
  | ^"N"
}

// Hash State Columns
hash_state_col = { "hash_state" }

hash_state_show             =  { hash_state_col ~ hash_state_display_modifier? }
hash_state_display_modifier = _{ WS* ~ "@" ~ WS* ~ hash_state_display_mode }
hash_state_display_mode     =  { full | short }

hash_state_filter        =  { hash_state_col ~ hash_state_filter_body }
hash_state_filter_body   = _{ WS* ~ COLON ~ WS* ~ LPAREN ~ WS* ~ hash_state_filter_values ~ WS* ~ RPAREN }
hash_state_filter_values = _{ hash_state_spec ~ (WS* ~ COMMA ~ WS* ~ hash_state_spec)* }
hash_state_spec          = _{ null | not_null | hash_state }
hash_state               =  {
    "UNKNOWN"
  | "unknown"
  | "Unknown"
  | "VALID"
  | "valid"
  | "Valid"
  | "SUSPECT"
  | "suspect"
  | "Suspect"
  | ^"U"
  | ^"V"
  | ^"S"
}

// Item Type Columns
item_type_col = { "item_type" }

item_type_show         =  { item_type_col ~ item_type_modifier? }
item_type_modifier     = _{ WS* ~ "@" ~ WS* ~ item_type_display_mode }
item_type_display_mode =  { full | short }

item_type_filter        =  { item_type_col ~ item_type_filter_body }
item_type_filter_body   = _{ WS* ~ COLON ~ WS* ~ LPAREN ~ item_type_filter_values ~ WS* ~ RPAREN }
item_type_filter_values = _{ item_type ~ (WS* ~ COMMA ~ WS* ~ item_type)* }
item_type               =  {
    "FILE"
  | "file"
  | "File"
  | "DIRECTORY"
  | "directory"
  | "Directory"
  | "DIR"
  | "dir"
  | "Dir"
  | "SYMLINK"
  | "symlink"
  | "Symlink"
  | "UNKNOWN"
  | "unknown"
  | "Unknown"
  | ^"F"
  | ^"D"
  | ^"S"
  | ^"U"
}

// Scan State Columns
scan_state_col = { "scan_state" }

scan_state_show         =  { scan_state_col ~ scan_state_modifier? }
scan_state_modifier     = _{ WS* ~ "@" ~ WS* ~ scan_state_display_mode }
scan_state_display_mode =  { full | short }

scan_state_filter        =  { scan_state_col ~ scan_state_filter_body }
scan_state_filter_body   = _{ WS* ~ COLON ~ WS* ~ LPAREN ~ WS* ~ scan_state_filter_values ~ WS* ~ RPAREN }
scan_state_filter_values = _{ scan_state ~ (WS* ~ COMMA ~ WS* ~ scan_state)* }
scan_state               =  {
    "SCANNING"
  | "scanning"
  | "Scanning"
  | "SWEEPING"
  | "sweeping"
  | "Sweeping"
  | "ANALYZING FILES"
  | "analyzing files"
  | "Analyzing Files"
  | "ANALYZINGFILES"
  | "analyzingfiles"
  | "AnalyzingFiles"
  | "ANALYZING SCAN"
  | "analyzing scan"
  | "Analyzing Scan"
  | "ANALYZINGSCAN"
  | "analyzingscan"
  | "AnalyzingScan"
  | "ANALYZING"
  | "analyzing"
  | "Analyzing"
  | "COMPLETED"
  | "completed"
  | "Completed"
  | "STOPPED"
  | "stopped"
  | "Stopped"
  | "ERROR"
  | "error"
  | "Error"
  | ^"AF"
  | ^"AS"
  | ^"A"
  | ^"S"
  | ^"W"
  | ^"C"
  | ^"P"
  | ^"E"
}

// Access Columns
access_col = { "access" }

access_show         =  { access_col ~ access_modifier? }
access_modifier     = _{ WS* ~ "@" ~ WS* ~ access_display_mode }
access_display_mode =  { full | short }

access_filter        =  { access_col ~ access_filter_body }
access_filter_body   = _{ WS* ~ COLON ~ WS* ~ LPAREN ~ WS* ~ access_filter_values ~ WS* ~ RPAREN }
access_filter_values = _{ access_spec ~ (WS* ~ COMMA ~ WS* ~ access_spec)* }
access_spec          = _{ null | not_null | access }
access               =  {
    "NO ERROR"
  | "no error"
  | "No Error"
  | "NOERROR"
  | "noerror"
  | "NoError"
  | "META ERROR"
  | "meta error"
  | "Meta Error"
  | "METAERROR"
  | "metaerror"
  | "MetaError"
  | "READ ERROR"
  | "read error"
  | "Read Error"
  | "READERROR"
  | "readerror"
  | "ReadError"
  | ^"N"
  | ^"M"
  | ^"R"
}

// Integer columns
int_col  = { "size" | "file_count" | "folder_count" | "total_size" | "new_hash_suspect_count" | "new_val_invalid_count" | "add_count" | "modify_count" | "delete_count" | "unchanged_count" | "val_unknown_count" | "val_valid_count" | "val_invalid_count" | "val_no_validator_count" | "hash_unknown_count" | "hash_baseline_count" | "hash_suspect_count" }
int_show = { int_col }

int_filter      =  { int_col ~ int_filter_body }
int_filter_body = _{ WS* ~ COLON ~ WS* ~ LPAREN ~ WS* ~ int_comparator ~ WS* ~ RPAREN }
int_comparator  = _{ (GT | LT) ~ WS* ~ int_val }
int_val         =  { WS* ~ ASCII_DIGIT* }

null     = { "-" | "NULL" | "null" }
not_null = { "not null" | "NOT NULL" }

default = { "default" | "DEFAULT" }
all     = { "all" | "ALL" }

// Format specifiers
short     = _{ "SHORT" | "short" }
full      = _{ "FULL" | "full" }
name      = _{ "NAME" | "name" }
relative  = _{ "RELATIVE" | "relative" }
timestamp = _{ "TIMESTAMP" | "timestamp" }

// Basic tokens - all silent
WS     = _{ " " | "\t" | NEWLINE }
COLON  = _{ ":" }
COMMA  = _{ "," }
DOTDOT = _{ ".." }
LPAREN = _{ "(" }
RPAREN = _{ ")" }
GT     =  { ">" }
LT     =  { "<" }
