///|
/// 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.
///
/// Get the root with `NodeRef::of_result` (or `Tree::root`), then go down
/// with `field` or `children`:
///
/// ```mbt check
/// test {
///   let src = "module Main exposing (..)\n\nadd x y =\n    x + y\n"
///   let result = @parser.parse_module(
///     @scanner.SourceText::new(src),
///     @scanner.DefaultScanner::new(),
///   )
///   let root = @syntax.NodeRef::of_result(result).unwrap()
///   let decl = root.field("declarations")[0]
///   inspect("\{decl.category()}/\{decl.kind()}", content="declaration/function")
///   // Typed code matches the case and reads the AST value.
///   match decl {
///     Declaration(node, _) => inspect(node.range.start.row, content="3")
///     _ => fail("not a declaration")
///   }
/// }
/// ```
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
}

///|
/// `x == y` element by element, with `same_value` for each element. A view
/// is a value on some targets (`physical_equal` on two copies of one view is
/// `false` on native), so views are compared by their elements.
fn[T : Eq] same_view(x : ArrayView[T], y : ArrayView[T]) -> Bool {
  guard x.length() == y.length() else { return false }
  for i in 0.. Bool {
  if physical_equal(a, b) {
    return true
  }
  match (a, b) {
    (File(x, g), File(y, h)) => same_value(x, y) && same_view(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_view(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.
///
/// ```mbt check
/// test {
///   let scanner = @scanner.DefaultScanner::new()
///   let ok = @parser.parse_module(
///     @scanner.SourceText::new("module Main exposing (..)\n\nx = 1\n"),
///     scanner,
///   )
///   let kind = @syntax.NodeRef::of_result(ok).map(n => n.kind())
///   debug_inspect(kind, content="Some(\"file\")")
///   // A missing module header gives no AST, so there is no root.
///   let bad = @parser.parse_module(@scanner.SourceText::new("x = 1\n"), scanner)
///   inspect(@syntax.NodeRef::of_result(bad) is None, content="true")
/// }
/// ```
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. A kind alone does not identify a node type: compare
/// `category()` too.
///
/// ```mbt check
/// test {
///   let src = "module Main exposing (..)\n\nf [] = []\n"
///   let result = @parser.parse_module(
///     @scanner.SourceText::new(src),
///     @scanner.DefaultScanner::new(),
///   )
///   let root = @syntax.NodeRef::of_result(result).unwrap()
///   let implementation = root.field("declarations")[0].field("declaration")[0]
///   let argument = implementation.field("arguments")[0]
///   let body = implementation.field("expression")[0]
///   inspect("\{argument.category()}/\{argument.kind()}", content="pattern/list")
///   inspect("\{body.category()}/\{body.kind()}", content="expression/list")
/// }
/// ```
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). Rows and columns start at 1;
/// the end is exclusive.
///
/// ```mbt check
/// test {
///   let src = "module Main exposing (..)\n\nadd x y =\n    x + y\n"
///   let result = @parser.parse_module(
///     @scanner.SourceText::new(src),
///     @scanner.DefaultScanner::new(),
///   )
///   let root = @syntax.NodeRef::of_result(result).unwrap()
///   let show = (r : @ast.Range) => {
///     "\{r.start.row}:\{r.start.column}-\{r.end.row}:\{r.end.column}"
///   }
///   inspect(show(root.range()), content="1:1-4:10")
///   inspect(show(root.field("declarations")[0].range()), content="3:1-4:10")
/// }
/// ```
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
  }
}