///|
/// A read-only reference to one node of a parse result. Each case points into
/// the typed AST (nothing is copied). Generic code uses `category`, `kind`,
/// `range`, `children` and `field`; typed code matches the cases.
///
/// Kinds and field names follow the JSON that elm-syntax (and `@ast.encode_*`)
/// writes. elm-syntax reuses tags across node types (`record`, `list`, `unit`,
/// …), so `(category, kind)` identifies a node type.
pub(all) enum NodeRef {
  File(@ast.File, ArrayView[@parser.AttributeGroup])
  Module(@ast.Node[@ast.Module])
  ModuleName(@ast.Node[@ast.ModuleName])
  Exposing(@ast.Node[@ast.Exposing])
  Expose(@ast.Node[@ast.TopLevelExpose])
  Import(@ast.Node[@ast.Import])
  Declaration(@ast.Node[@ast.Declaration], ArrayView[@parser.DocAttribute])
  Documentation(@ast.Node[String])
  Signature(@ast.Node[@ast.Signature])
  Implementation(@ast.Node[@ast.FunctionImplementation])
  Constructor(@ast.Node[@ast.ValueConstructor])
  Expression(@ast.Node[@ast.Expression])
  LetDeclaration(@ast.Node[@ast.LetDeclaration])
  Case(@ast.Case)
  RecordSetter(@ast.Node[@ast.RecordSetter])
  Pattern(@ast.Node[@ast.Pattern])
  TypeAnnotation(@ast.Node[@ast.TypeAnnotation])
  RecordField(@ast.Node[@ast.RecordField])
  Name(@ast.Node[String])
  Comment(@ast.Node[String])
  Attribute(@parser.DocAttribute)
} derive(Eq, Debug)

///|
fn[T : Eq] same_value(x : T, y : T) -> Bool {
  physical_equal(x, y) || x == y
}

///|
/// `a == b`, but fast for nodes taken from the same tree: nodes wrap the AST
/// values themselves, so the same node is usually the same object and the
/// structural comparison of whole subtrees is not needed.
fn same(a : NodeRef, b : NodeRef) -> Bool {
  match (a, b) {
    (File(x, g), File(y, h)) => same_value(x, y) && same_value(g, h)
    (Module(x), Module(y)) => same_value(x, y)
    (ModuleName(x), ModuleName(y)) => same_value(x, y)
    (Exposing(x), Exposing(y)) => same_value(x, y)
    (Expose(x), Expose(y)) => same_value(x, y)
    (Import(x), Import(y)) => same_value(x, y)
    (Declaration(x, g), Declaration(y, h)) =>
      same_value(x, y) && same_value(g, h)
    (Documentation(x), Documentation(y)) => same_value(x, y)
    (Signature(x), Signature(y)) => same_value(x, y)
    (Implementation(x), Implementation(y)) => same_value(x, y)
    (Constructor(x), Constructor(y)) => same_value(x, y)
    (Expression(x), Expression(y)) => same_value(x, y)
    (LetDeclaration(x), LetDeclaration(y)) => same_value(x, y)
    (Case(x), Case(y)) => same_value(x, y)
    (RecordSetter(x), RecordSetter(y)) => same_value(x, y)
    (Pattern(x), Pattern(y)) => same_value(x, y)
    (TypeAnnotation(x), TypeAnnotation(y)) => same_value(x, y)
    (RecordField(x), RecordField(y)) => same_value(x, y)
    (Name(x), Name(y)) => same_value(x, y)
    (Comment(x), Comment(y)) => same_value(x, y)
    (Attribute(x), Attribute(y)) => same_value(x, y)
    _ => false
  }
}

///|
/// The root node of `result`: its file with the doc attributes, or `None`
/// when the parse produced no AST.
///
/// The attribute groups are copied; the AST is shared with `result`, so
/// neither may be changed while nodes are in use.
pub fn NodeRef::of_result(result : @parser.ParseResult) -> NodeRef? {
  result.ast.map(file => File(file, result.attributes.copy()))
}

///|
/// The node type: `"expression"`, `"pattern"`, `"type"`, `"declaration"`, …
pub fn NodeRef::category(self : NodeRef) -> String {
  match self {
    File(_, _) => "file"
    Module(_) => "module"
    ModuleName(_) => "module_name"
    Exposing(_) => "exposing"
    Expose(_) => "expose"
    Import(_) => "import"
    Declaration(_, _) => "declaration"
    Documentation(_) => "documentation"
    Signature(_) => "signature"
    Implementation(_) => "implementation"
    Constructor(_) => "constructor"
    Expression(_) => "expression"
    LetDeclaration(_) => "let_declaration"
    Case(_) => "case_branch"
    RecordSetter(_) => "record_setter"
    Pattern(_) => "pattern"
    TypeAnnotation(_) => "type"
    RecordField(_) => "record_field"
    Name(_) => "name"
    Comment(_) => "comment"
    Attribute(_) => "attribute"
  }
}

///|
/// The elm-syntax JSON tag of the node, or its category when elm-syntax has no
/// tag for it.
pub fn NodeRef::kind(self : NodeRef) -> String {
  match self {
    Module(m) =>
      match m.value {
        NormalModule(_) => "normal"
        PortModule(_) => "port"
        EffectModule(_) => "effect"
      }
    Exposing(x) =>
      match x.value {
        All(_) => "all"
        Explicit(_) => "explicit"
      }
    Expose(x) =>
      match x.value {
        InfixExpose(_) => "infix"
        FunctionExpose(_) => "function"
        TypeOrAliasExpose(_) => "typeOrAlias"
        TypeExpose(_) => "typeexpose"
      }
    Declaration(d, _) =>
      match d.value {
        FunctionDeclaration(_) => "function"
        AliasDeclaration(_) => "typeAlias"
        CustomTypeDeclaration(_) => "typedecl"
        PortDeclaration(_) => "port"
        InfixDeclaration(_) => "infix"
        Destructuring(_, _) => "destructuring"
      }
    LetDeclaration(d) =>
      match d.value {
        LetFunction(_) => "function"
        LetDestructuring(_, _) => "destructuring"
      }
    Expression(x) => expression_kind(x.value)
    Pattern(x) => pattern_kind(x.value)
    TypeAnnotation(x) => type_kind(x.value)
    Attribute(Attribute(..)) => "attribute"
    Attribute(Docs(..)) => "docs"
    other => other.category()
  }
}

///|
fn expression_kind(e : @ast.Expression) -> String {
  match e {
    UnitExpr => "unit"
    Application(_) => "application"
    OperatorApplication(_, _, _, _) => "operatorapplication"
    FunctionOrValue(_, _) => "functionOrValue"
    IfBlock(_, _, _) => "ifBlock"
    PrefixOperator(_) => "prefixoperator"
    Operator(_) => "operator"
    Hex(_) => "hex"
    Integer(_) => "integer"
    Floatable(_) => "float"
    Negation(_) => "negation"
    Literal(_) => "literal"
    CharLiteral(_) => "charLiteral"
    TupledExpression(_) => "tupled"
    ListExpr(_) => "list"
    ParenthesizedExpression(_) => "parenthesized"
    LetExpression(_) => "let"
    CaseExpression(_) => "case"
    LambdaExpression(_) => "lambda"
    RecordAccess(_, _) => "recordAccess"
    RecordAccessFunction(_) => "recordAccessFunction"
    RecordExpr(_) => "record"
    RecordUpdateExpression(_, _) => "recordUpdate"
    GLSLExpression(_) => "glsl"
  }
}

///|
fn pattern_kind(p : @ast.Pattern) -> String {
  match p {
    AllPattern => "all"
    UnitPattern => "unit"
    CharPattern(_) => "char"
    StringPattern(_) => "string"
    HexPattern(_) => "hex"
    IntPattern(_) => "int"
    FloatPattern(_) => "float"
    TuplePattern(_) => "tuple"
    RecordPattern(_) => "record"
    UnConsPattern(_, _) => "uncons"
    ListPattern(_) => "list"
    VarPattern(_) => "var"
    NamedPattern(_, _) => "named"
    AsPattern(_, _) => "as"
    // elm-syntax's own spelling.
    ParenthesizedPattern(_) => "parentisized"
  }
}

///|
fn type_kind(t : @ast.TypeAnnotation) -> String {
  match t {
    GenericType(_) => "generic"
    Typed(_, _) => "typed"
    Unit => "unit"
    Tupled(_) => "tupled"
    FunctionTypeAnnotation(_, _) => "function"
    Record(_) => "record"
    GenericRecord(_, _) => "genericRecord"
  }
}

///|
fn later(a : @ast.Location, b : @ast.Location) -> @ast.Location {
  if a.row > b.row || (a.row == b.row && a.column > b.column) {
    a
  } else {
    b
  }
}

///|
fn earlier(a : @ast.Location, b : @ast.Location) -> @ast.Location {
  if later(a, b) == a {
    b
  } else {
    a
  }
}

///|
/// The node's range. A case branch runs from its pattern to its expression;
/// the file from line 1, column 1 to the end of its last node. A declaration
/// with doc attributes starts no later than its first attribute (a port's doc
/// comment is not part of its elm-syntax range).
pub fn NodeRef::range(self : NodeRef) -> @ast.Range {
  match self {
    File(file, _) => {
      let mut end = file.module_definition.range.end
      for i in file.imports {
        end = later(end, i.range.end)
      }
      for d in file.declarations {
        end = later(end, d.range.end)
      }
      for c in file.comments {
        end = later(end, c.range.end)
      }
      { start: { row: 1, column: 1, }, end, }
    }
    Module(x) => x.range
    ModuleName(x) => x.range
    Exposing(x) => x.range
    Expose(x) => x.range
    Import(x) => x.range
    Declaration(x, attributes) => {
      let mut start = x.range.start
      for a in attributes {
        start = earlier(start, Attribute(a).range().start)
      }
      { start, end: x.range.end, }
    }
    Documentation(x) => x.range
    Signature(x) => x.range
    Implementation(x) => x.range
    Constructor(x) => x.range
    Expression(x) => x.range
    LetDeclaration(x) => x.range
    Case(c) => { start: c.pattern.range.start, end: c.expression.range.end, }
    RecordSetter(x) => x.range
    Pattern(x) => x.range
    TypeAnnotation(x) => x.range
    RecordField(x) => x.range
    Name(x) => x.range
    Comment(x) => x.range
    Attribute(Attribute(range~, ..)) => range
    Attribute(Docs(range~, ..)) => range
  }
}