/*
 * Peggy Parser for the Evidence DSL
 *
 * This grammar defines a declarative language for authorization policies.
 * It parses definitions, facts, measures, and evidence rules into a structured
 * Abstract Syntax Tree (AST) represented by plain JavaScript objects.
 * (Version 4: Corrected infinite loop check in String literal parsing)
 */
{
  // The location() function provides line/column info for error reporting.
  // The text() function returns the matched text for a rule.
  
  // Helper function to build a left-associative binary expression tree.
  function buildLeftAssoc(head, tail) {
    return tail.reduce((result, element) => {
      return {
        type: "BinaryExpression",
        operator: element[1],
        left: result,
        right: element[3],
        location: location()
      };
    }, head);
  }
}

// -- Grammar Entry Point --
Program
  = _ statements:(Statement _)* _ {
      const allStatements = statements.map(s => s[0]);
      return { 
        type: "Program", 
        body: allStatements,
        definitions: allStatements.filter(s => s.type === "Definition"),
        facts: allStatements.filter(s => s.type === "Fact"),
        evidence: allStatements.filter(s => s.type === "Evidence"),
        measures: allStatements.filter(s => s.type === "Measure"),
        sources: allStatements.filter(s => s.type === "Source")
      };
    }

Statement
  = Definition
  / Source
  / Fact
  / Evidence
  / Measure

// -- Top-Level Statements --

Definition "A type definition"
  = ("definition" / "type") __ name:Identifier __ "{" _ fields:(Field _)* "}" {
      return { type: "Definition", name, fields: fields.map(f => f[0]) };
    }

Field
  = name:Identifier _ ":" _ fieldType:Type _ isArray:("[]")? _ behavior:Behavior? _ cache:CacheDirective? {
      return {
        type: "Field",
        name,
        fieldType,
        isArray: !!isArray,
        behavior: behavior || null,
        cache: cache || null
      };
    }

Fact "A statement of fact (or relation in ADR-000)"
  = ("fact" / "relation") __ star:"*"? name:Identifier _ "(" _ params:ParameterList? _ ")" _ behavior:BehaviorAnnotation? _ properties:(FactProperty _)* cache:CacheDirective? _ limit:Limit? {
      return {
        type: "Fact",
        name,
        params: params || [],
        behavior: behavior || null,
        properties: properties.map(p => p[0]),
        cache: cache || null,
        limit: limit || null,
        injectable: !!star
      };
    }

Source "An injectable source (proof provider)"
  = "source" __ star:"*"? name:Identifier _ "(" _ params:ParameterList? _ ")" _ provides:Provides? _ within:WithinClause? {
      return {
        type: "Source",
        name,
        params: params || [],
        provides: provides || null,
        injectable: !!star,
        within: within || null
      };
    }

WithinClause "A freshness constraint on a source"
  = "within" __ duration:Duration { return duration; }

Evidence "An evidence rule"
  = "evidence" __ star:"*"? name:Identifier _ "(" _ params:ParameterList? _ ")" _ limit:Limit? _ "{" _ body:EvidenceBody _ "}" _ provides:Provides? {
      return {
        type: "Evidence",
        name,
        params: params || [],
        limit: limit || null,
        body,
        provides: provides || null,
        challenge: !!star
      };
    }

Measure "A derived measurement or value"
  = "measure" __ name:Identifier _ "(" _ params:ParameterList? _ ")" _ "{" _ body:MeasureBody _ "}" _ provides:Provides? {
      return {
        type: "Measure",
        name,
        params: params || [],
        body,
        provides: provides || null
      };
    }

// -- Evidence & Measure Internals --

EvidenceBody
  = statements:(EvidenceStatement _)* {
      return { type: "EvidenceBody", statements: statements.map(s => s[0]) };
    }

EvidenceStatement
  = DefeasibleLogic
  / Fusion
  / CollectionProcessing
  / PatternMatch
  / Expression

MeasureBody
  = statements:(MeasureStatement _)* returnStmt:ReturnStatement? {
      return { 
        type: "MeasureBody", 
        statements: statements.map(s => s[0]), 
        returnStatement: returnStmt || null 
      };
    }

MeasureStatement
  = Fusion
  / Aggregation
  / PatternMatch
  / Expression

ReturnStatement
  = "return" __ expression:Expression {
      return { type: "ReturnStatement", expression };
    }

// -- Complex Statement Types --

DefeasibleLogic
  = type:("NEVER" / "ALWAYS" / "REQUIRES") __ condition:Expression {
      return { type: "DefeasibleLogic", logicType: type, condition };
    }
  / "WHEN" __ condition:Expression __ "UNLESS" __ defeater:Expression {
      return { type: "DefeasibleLogic", logicType: "WHEN", condition, defeater };
    }
  / "WHEN" __ condition:Expression {
      return { type: "DefeasibleLogic", logicType: "WHEN", condition };
    }

PatternMatch
  = predicate:PatternPredicate _ binding:BindingClause? _ "{" _ body:EvidenceBody _ "}" _ limit:Limit? _ withClause:WithClause? {
      return {
        type: "PatternMatch",
        predicate,
        binding: binding || null,
        limit: limit || null,
        body,
        withClause: withClause || null
      };
    }

CollectionProcessing
  = measure:Expression _ "|" _ variable:Identifier _ "|" _ fusionStrategy:("fusion" __ strategy:Identifier)? _ "{" _ body:EvidenceBody _ "}" _ limit:Limit? {
      return {
        type: "CollectionProcessing",
        measure,
        variable,
        fusion: fusionStrategy ? { strategy: fusionStrategy[1] } : null,
        body,
        limit: limit || null
      };
    }

PatternPredicate
  = name:Identifier _ "(" _ args:PatternArgumentList? _ ")" {
      return { type: "Predicate", name, args: args || [] };
    }

PatternArgumentList
  = head:PatternArgument tail:(_ "," _ arg:PatternArgument)* {
      return [head, ...tail.map(t => t[3])];
    }
    
PatternArgument
  = "*" _ name:Identifier { return { type: "Wildcard", name }; }
  / Expression

BindingClause
  = "|" _ name:Identifier _ "|" { return name; }

WithClause
  = "with" __ condition:Expression { return condition; }

Fusion
  = "fusion" __ strategy:Identifier __ "{" _ expressions:ExpressionList _ "}" {
      return { type: "Fusion", strategy, expressions };
    }

Aggregation
  = "aggregate" __ "{" _ expressions:ExpressionList _ "}" _ using:Using? {
      return { type: "Aggregation", expressions, using: using || null };
    }

Using
  = "USING" __ method:Identifier { return method; }

// -- Type System & Parameters --

Type
  = Identifier

TypeName
  = name:Identifier { return { type: "TypeName", name }; }
  / literal:String { return { type: "TypeName", name: literal.value }; }

ParameterList
  = head:Parameter tail:(_ "," _ param:Parameter)* {
      return [head, ...tail.map(t => t[3])];
    }

Parameter
  = name:Identifier _ ":" _ paramType:Type _ isArray:("[]")? {
      return { type: "Parameter", name, paramType, isArray: !!isArray };
    }

Provides
  = "PROVIDES" __ providesType:Type { return providesType; }

BehaviorAnnotation
  = "BEHAVES" __ "AS" __ behavior:("edge" / "transitive" / "hierarchical" / "symmetrical_graph") {
      return { type: "BehaviorAnnotation", behavior };
    }

FactProperty
  = "transitive" { return "transitive"; }
  / "symmetrical" { return "symmetrical"; }

Limit
  = "limit" __ value:Integer { return value; }

// -- Behaviors and Caching --

Behavior
  = "BEHAVES" __ "{" _ b:(DecayBehavior / BlurBehavior / TTLBehavior) _ "}" { return b; }

DecayBehavior
  = "decaying" __ direction:("up" / "down" / "neutral" / "stable") __ period:("hourly" / "daily" / "weekly" / "monthly") {
      return { type: "Behavior", behaviorType: "decay", direction, period };
    }

BlurBehavior
  = "blurring" __ mode:("fixed" / "adaptive" / "confidence") confidence:(__ ("confidence_90" / "confidence_95" / "confidence_99"))? {
      return { type: "Behavior", behaviorType: "blur", mode, confidence: confidence ? confidence[1] : null };
    }

TTLBehavior
  = "ttl" __ duration:Duration {
      return { type: "Behavior", behaviorType: "ttl", duration };
    }

CacheDirective
  = "CACHE" __ directive:("eager" / "lazy") { return directive; }

// -- Expressions (with operator precedence) --

Expression
  = LogicalOr

LogicalOr
  = head:LogicalAnd tail:(_ "||" _ right:LogicalAnd)* { return buildLeftAssoc(head, tail); }

LogicalAnd
  = head:Comparison tail:(_ "&&" _ right:Comparison)* { return buildLeftAssoc(head, tail); }

Comparison
  = head:TemporalComparison _ "is" __ typeName:TypeName {
      return { type: "BinaryExpression", operator: "is", left: head, right: typeName };
    }
  / head:TemporalComparison tail:(_ operator:("==" / "!=" / ">=" / "<=" / ">" / "<") _ right:TemporalComparison)* { return buildLeftAssoc(head, tail); }

TemporalComparison
  = head:Addition _ "within" __ right:Duration {
      return { type: "BinaryExpression", operator: "within", left: head, right };
    }
  / Addition

Addition
  = head:Multiplication tail:(_i operator:("+" / "-") _i right:Multiplication)* { return buildLeftAssoc(head, tail); }

Multiplication
  = head:Unary tail:(_i operator:("*" / "/") _i right:Unary)* { return buildLeftAssoc(head, tail); }

Unary
  = operator:("NOT" / "!") __ operand:Unary { return { type: "UnaryExpression", operator: "NOT", operand }; }
  / Postfix

Postfix
  = primary:(AttributeAccess / PrimaryTerm) binding:BindingClause? {
      if (binding) {
        return { type: "BindingAccess", expression: primary, binding };
      }
      return primary;
    }

AttributeAccess
  = head:PrimaryTerm tail:(_ "." _ attr:Identifier)+ {
      return tail.reduce((obj, part) => {
        return {
          type: "AttributeAccess",
          object: obj,
          attribute: part[3], // The Identifier is the 4th element (index 3)
          location: location()
        };
      }, head);
    }

PrimaryTerm "The non-recursive base for an expression chain"
  = ChallengePredicate
  / Literal
  / PredicateCall
  / Variable
  / "(" _ expr:Expression _ ")" { return expr; }

ChallengePredicate
  = "*" name:Identifier _ "(" _ args:ArgumentList? _ ")" {
      return { type: "PredicateCall", name, args: args || [], challenge: true };
    }
  
PredicateCall
  = name:Identifier "(" _ args:ArgumentList? _ ")" {
      return { type: "PredicateCall", name, args: args || [] };
    }

Variable
  = name:Identifier { return { type: "Variable", name }; }

ArgumentList
  = head:Expression tail:(_ "," _ expr:Expression)* {
      return [head, ...tail.map(t => t[3])];
    }
    
ExpressionList
  = head:Expression tail:(_ "," _ expr:Expression)* {
      return [head, ...tail.map(t => t[3])];
    }

// -- Literals --

Literal
  = String / Float / Integer / Boolean / Duration

String "A string literal"
  = '"' chars:((!("\"" / "\\")) . / "\\" .)* '"' {
      return { type: "Literal", value: JSON.parse(text()) };
    }
  / "'" chars:((!("'" / "\\")) . / "\\" .)* "'" {
      return { type: "Literal", value: JSON.parse("\"" + chars.map(c => c[0] === '\\' ? c[1] : c[1]).join('') + "\"") };
    }

Float "A floating-point number"
  = value:([0-9]+ "." [0-9]+) { return { type: "Literal", value: parseFloat(text()) }; }

Integer "An integer"
  = value:[0-9]+ { return { type: "Literal", value: parseInt(text(), 10) }; }

Boolean "A boolean literal"
  = value:("true" / "false") { return { type: "Literal", value: value === "true" }; }

Duration "A time duration literal"
  = value:([0-9]+ ("h" / "d" / "w" / "m")) { return { type: "Literal", value: text(), unit: text().slice(-1) }; }


// -- Core Tokens & Whitespace --

Identifier
  = !Keyword name:$([a-zA-Z_][a-zA-Z0-9_]*) { return name; }

Keyword
  = ("definition" / "type" / "fact" / "relation" / "evidence" / "measure" / "BEHAVES" / "AS" / "CACHE"
  / "decaying" / "blurring" / "ttl" / "transitive" / "symmetrical" / "hierarchical" / "symmetrical_graph" / "edge" / "limit"
  / "PROVIDES" / "fusion" / "aggregate" / "USING" / "NEVER" / "ALWAYS" / "WHEN" / "UNLESS"
  / "REQUIRES" / "with" / "true" / "false" / "NOT" / "within" / "return" / "is") !([a-zA-Z0-9_])

// _  = optional whitespace and comments
// __ = mandatory whitespace and comments
_
  = (WhiteSpace / Comment)*

// Inline (single-line) optional whitespace — used around arithmetic
// operators so a `*` challenge-predicate on the next line is not
// absorbed as a multiplication tail.
_i
  = [ \t]*
__
  = (WhiteSpace / Comment)+

WhiteSpace
  = [ \t\r\n]

Comment
  = "//" [^\r\n]*
  / "/*" (!"*/" .)* "*/"
