// 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? ~ show_clause? ~ order_clause? ~ limit_clause? ~ offset_clause? ~ WS* ~ EOI }

query_type = { "roots" | "scans" | "items" | "changes" | "alerts" }

// 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 | path_filter | val_filter | item_type_filter | change_type_filter | alert_type_filter | alert_status_filter | scan_state_filter | access_filter | int_filter }

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

// 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 =  { id_col | date_col | bool_col | string_col | path_col | val_col | item_type_col | change_type_col | alert_type_col | alert_status_col | scan_state_col | access_col | int_col }
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 }
path_filter_EOI         = { WS* ~ path_filter_values ~ WS* ~ EOI }
val_filter_EOI          = { WS* ~ val_filter_values ~ WS* ~ EOI }
item_type_filter_EOI    = { WS* ~ item_type_filter_values ~ WS* ~ EOI }
change_type_filter_EOI  = { WS* ~ change_type_filter_values ~ WS* ~ EOI }
alert_type_filter_EOI   = { WS* ~ alert_type_filter_values ~ WS* ~ EOI }
alert_status_filter_EOI = { WS* ~ alert_status_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 = { "alert_id" | "root_id" | "scan_id" | "item_id" | "change_id" | "last_scan" | "last_hash_scan_old" | "last_hash_scan" | "last_val_scan_old" | "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_old" | "mod_date_new" | "mod_date" | "created_at" | "updated_at" | "deleted_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 ~ date }
date               =  { WS* ~ ASCII_DIGIT{4} ~ "-" ~ ASCII_DIGIT{2} ~ "-" ~ ASCII_DIGIT{2} }

// Bool Columns
bool_col = { "is_undelete" | "is_hash" | "hash_all" | "is_val" | "val_all" | "hash_change" | "meta_change" | "val_change" | "is_ts" | "was_restarted" }

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" | "file_hash" | "val_error_old" | "val_error_new" | "val_error" | "hash_old" | "hash_new" }

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)))* }

// Path Columns
path_col = { "item_path" | "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 Columns
val_col = { "val_old" | "val_new" | "val" }

val_show             =  { val_col ~ val_display_modifier? }
val_display_modifier = _{ WS* ~ "@" ~ WS* ~ val_display_mode }
val_display_mode     =  { full | short }

val_filter        =  { val_col ~ val_filter_body }
val_filter_body   = _{ WS* ~ COLON ~ WS* ~ LPAREN ~ WS* ~ val_filter_values ~ WS* ~ RPAREN }
val_filter_values = _{ val_spec ~ (WS* ~ COMMA ~ WS* ~ val_spec)* }
val_spec          = _{ null | not_null | val }
val               =  {
    "UNKNOWN"
  | "unknown"
  | "Unknown"
  | "VALID"
  | "valid"
  | "Valid"
  | "INVALID"
  | "invalid"
  | "Invalid"
  | "NO VALIDATOR"
  | "no validator"
  | "No Validator"
  | "NOVALIDATOR"
  | "novalidator"
  | "NoValidator"
  | ^"U"
  | ^"V"
  | ^"I"
  | ^"N"
}

// 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"
}

// Change Type Columns
change_type_col = { "change_type" }

change_type_show         =  { change_type_col ~ change_type_modifier? }
change_type_modifier     = _{ WS* ~ "@" ~ WS* ~ change_type_display_mode }
change_type_display_mode =  { full | short }

change_type_filter        =  { change_type_col ~ change_type_filter_body }
change_type_filter_body   = _{ WS* ~ COLON ~ WS* ~ LPAREN ~ WS* ~ change_type_filter_values ~ WS* ~ RPAREN }
change_type_filter_values = _{ change_type_spec ~ (WS* ~ COMMA ~ WS* ~ change_type_spec)* }
change_type_spec          = _{ null | not_null | change_type }
change_type               =  {
    "ADD"
  | "add"
  | "Add"
  | "DELETE"
  | "delete"
  | "Delete"
  | "MODIFY"
  | "modify"
  | "Modify"
  | "NO CHANGE"
  | "no change"
  | "No Change"
  | "NOCHANGE"
  | "nochange"
  | "NoChange"
  | ^"A"
  | ^"D"
  | ^"M"
  | ^"N"
}

alert_type_col = { "alert_type" }

alert_type_show         =  { alert_type_col ~ alert_type_modifier? }
alert_type_modifier     = _{ WS* ~ "@" ~ WS* ~ alert_type_display_mode }
alert_type_display_mode =  { full | short }

alert_type_filter        =  { alert_type_col ~ alert_type_filter_body }
alert_type_filter_body   = _{ WS* ~ COLON ~ WS* ~ LPAREN ~ WS* ~ alert_type_filter_values ~ WS* ~ RPAREN }
alert_type_filter_values = _{ alert_type_spec ~ (WS* ~ COMMA ~ WS* ~ alert_type_spec)* }
alert_type_spec          = _{ null | not_null | alert_type }
alert_type               =  {
    "SUSPICIOUS HASH"
  | "suspicious hash"
  | "Suspicious Hash"
  | "SUSPICIOUSHASH"
  | "suspicioushash"
  | "SuspiciousHash"
  | "INVALID ITEM"
  | "invalid item"
  | "Invalid Item"
  | "INVALIDITEM"
  | "invaliditem"
  | "InvalidItem"
  | "ACCESS DENIED"
  | "access denied"
  | "Access Denied"
  | "ACCESSDENIED"
  | "accessdenied"
  | "AccessDenied"
  | ^"H"
  | ^"I"
  | ^"A"
}

alert_status_col = { "alert_status" }

alert_status_show         =  { alert_status_col ~ alert_status_modifier? }
alert_status_modifier     = _{ WS* ~ "@" ~ WS* ~ alert_status_display_mode }
alert_status_display_mode =  { full | short }

alert_status_filter        =  { alert_status_col ~ alert_status_filter_body }
alert_status_filter_body   = _{ WS* ~ COLON ~ WS* ~ LPAREN ~ WS* ~ alert_status_filter_values ~ WS* ~ RPAREN }
alert_status_filter_values = _{ alert_status_spec ~ (WS* ~ COMMA ~ WS* ~ alert_status_spec)* }
alert_status_spec          = _{ null | not_null | alert_status }
alert_status               =  {
    "OPEN"
  | "open"
  | "Open"
  | "FLAGGED"
  | "flagged"
  | "Flagged"
  | "DISMISSED"
  | "dismissed"
  | "Dismissed"
  | ^"O"
  | ^"F"
  | ^"D"
}

// 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"
  | "analyzing"
  | "Analyzing"
  | "COMPLETED"
  | "completed"
  | "Completed"
  | "STOPPED"
  | "stopped"
  | "Stopped"
  | "ERROR"
  | "error"
  | "Error"
  | ^"S"
  | ^"W"
  | ^"A"
  | ^"C"
  | ^"P"
  | ^"E"
}

// Access Columns (access_new/access_old must come before access to prevent partial matches)
access_col = { "access_new" | "access_old" | "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_new" | "size_old" | "size" | "file_count" | "folder_count" | "total_size" | "alert_count" | "add_count" | "modify_count" | "delete_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     =  { "<" }
