//! A typed layer over the untyped syntax tree.
//!
//! The AST is rooted in the [`MarkupNode`].

use std::num::NonZeroUsize;
use std::ops::Deref;

use super::{NodeData, NodeKind, RawFields, Span, SyntaxNode, Unit};
use crate::util::EcoString;

/// A typed AST node.
pub trait TypedNode: Sized {
    /// Convert a node into its typed variant.
    fn from_untyped(node: &SyntaxNode) -> Option<Self>;

    /// A reference to the underlying syntax node.
    fn as_untyped(&self) -> &SyntaxNode;

    /// The source code location.
    fn span(&self) -> Span {
        self.as_untyped().span()
    }
}

macro_rules! node {
    ($(#[$attr:meta])* $name:ident) => {
        node!{ $(#[$attr])* $name: NodeKind::$name { .. } }
    };
    ($(#[$attr:meta])* $name:ident: $variants:pat) => {
        #[derive(Debug, Clone, PartialEq, Hash)]
        #[repr(transparent)]
        $(#[$attr])*
        pub struct $name(SyntaxNode);

        impl TypedNode for $name {
            fn from_untyped(node: &SyntaxNode) -> Option<Self> {
                if matches!(node.kind(), $variants) {
                    Some(Self(node.clone()))
                } else {
                    None
                }
            }

            fn as_untyped(&self) -> &SyntaxNode {
                &self.0
            }
        }
    };
}

node! {
    /// The syntactical root capable of representing a full parsed document.
    Markup
}

impl Markup {
    /// The children.
    pub fn children(&self) -> impl DoubleEndedIterator<Item = MarkupNode> + '_ {
        let mut was_stmt = false;
        self.0
            .children()
            .filter(move |node| {
                // Ignore linebreak directly after statements without semicolons.
                let kind = node.kind();
                let keep = !was_stmt || !matches!(kind, NodeKind::Space { newlines: 1 });
                was_stmt = kind.is_stmt();
                keep
            })
            .filter_map(SyntaxNode::cast)
    }
}

/// A single piece of markup.
#[derive(Debug, Clone, PartialEq)]
pub enum MarkupNode {
    /// Whitespace.
    Space(Space),
    /// A forced line break: `\`.
    Linebreak(Linebreak),
    /// Plain text without markup.
    Text(Text),
    /// An escape sequence: `\#`, `\u{1F5FA}`.
    Escape(Escape),
    /// A shorthand for a unicode codepoint. For example, `~` for non-breaking
    /// space or `-?` for a soft hyphen.
    Shorthand(Shorthand),
    /// A smart quote: `'` or `"`.
    SmartQuote(SmartQuote),
    /// Strong content: `*Strong*`.
    Strong(Strong),
    /// Emphasized content: `_Emphasized_`.
    Emph(Emph),
    /// Raw text with optional syntax highlighting: `` `...` ``.
    Raw(Raw),
    /// A hyperlink: `https://typst.org`.
    Link(Link),
    /// A label: `<label>`.
    Label(Label),
    /// A reference: `@target`.
    Ref(Ref),
    /// A section heading: `= Introduction`.
    Heading(Heading),
    /// An item in an unordered list: `- ...`.
    List(ListItem),
    /// An item in an enumeration (ordered list): `+ ...` or `1. ...`.
    Enum(EnumItem),
    /// An item in a description list: `/ Term: Details`.
    Desc(DescItem),
    /// A math formula: `$x$`, `$ x^2 $`.
    Math(Math),
    /// An expression.
    Expr(Expr),
}

impl TypedNode for MarkupNode {
    fn from_untyped(node: &SyntaxNode) -> Option<Self> {
        match node.kind() {
            NodeKind::Space { .. } => node.cast().map(Self::Space),
            NodeKind::Linebreak => node.cast().map(Self::Linebreak),
            NodeKind::Text(_) => node.cast().map(Self::Text),
            NodeKind::Escape(_) => node.cast().map(Self::Escape),
            NodeKind::Shorthand(_) => node.cast().map(Self::Shorthand),
            NodeKind::SmartQuote { .. } => node.cast().map(Self::SmartQuote),
            NodeKind::Strong => node.cast().map(Self::Strong),
            NodeKind::Emph => node.cast().map(Self::Emph),
            NodeKind::Raw(_) => node.cast().map(Self::Raw),
            NodeKind::Link(_) => node.cast().map(Self::Link),
            NodeKind::Label(_) => node.cast().map(Self::Label),
            NodeKind::Ref(_) => node.cast().map(Self::Ref),
            NodeKind::Heading => node.cast().map(Self::Heading),
            NodeKind::ListItem => node.cast().map(Self::List),
            NodeKind::EnumItem => node.cast().map(Self::Enum),
            NodeKind::DescItem => node.cast().map(Self::Desc),
            NodeKind::Math => node.cast().map(Self::Math),
            _ => node.cast().map(Self::Expr),
        }
    }

    fn as_untyped(&self) -> &SyntaxNode {
        match self {
            Self::Space(v) => v.as_untyped(),
            Self::Linebreak(v) => v.as_untyped(),
            Self::Text(v) => v.as_untyped(),
            Self::Escape(v) => v.as_untyped(),
            Self::Shorthand(v) => v.as_untyped(),
            Self::SmartQuote(v) => v.as_untyped(),
            Self::Strong(v) => v.as_untyped(),
            Self::Emph(v) => v.as_untyped(),
            Self::Raw(v) => v.as_untyped(),
            Self::Link(v) => v.as_untyped(),
            Self::Label(v) => v.as_untyped(),
            Self::Ref(v) => v.as_untyped(),
            Self::Heading(v) => v.as_untyped(),
            Self::List(v) => v.as_untyped(),
            Self::Enum(v) => v.as_untyped(),
            Self::Desc(v) => v.as_untyped(),
            Self::Math(v) => v.as_untyped(),
            Self::Expr(v) => v.as_untyped(),
        }
    }
}

node! {
    /// Whitespace.
    Space
}

impl Space {
    /// Get the number of newlines.
    pub fn newlines(&self) -> usize {
        match self.0.kind() {
            &NodeKind::Space { newlines } => newlines,
            _ => panic!("space is of wrong kind"),
        }
    }
}

node! {
    /// A forced line break: `\`.
    Linebreak
}

node! {
    /// Plain text without markup.
    Text
}

impl Text {
    /// Get the text.
    pub fn get(&self) -> &EcoString {
        match self.0.kind() {
            NodeKind::Text(v) => v,
            _ => panic!("text is of wrong kind"),
        }
    }
}

node! {
    /// An escape sequence: `\#`, `\u{1F5FA}`.
    Escape
}

impl Escape {
    /// Get the escaped character.
    pub fn get(&self) -> char {
        match self.0.kind() {
            &NodeKind::Escape(v) => v,
            _ => panic!("escape is of wrong kind"),
        }
    }
}

node! {
    /// A shorthand for a unicode codepoint. For example, `~` for non-breaking
    /// space or `-?` for a soft hyphen.
    Shorthand
}

impl Shorthand {
    /// Get the shorthanded character.
    pub fn get(&self) -> char {
        match self.0.kind() {
            &NodeKind::Shorthand(v) => v,
            _ => panic!("shorthand is of wrong kind"),
        }
    }
}

node! {
    /// A smart quote: `'` or `"`.
    SmartQuote
}

impl SmartQuote {
    /// Whether this is a double quote.
    pub fn double(&self) -> bool {
        match self.0.kind() {
            &NodeKind::SmartQuote { double } => double,
            _ => panic!("smart quote is of wrong kind"),
        }
    }
}

node! {
    /// Strong content: `*Strong*`.
    Strong
}

impl Strong {
    /// The contents of the strong node.
    pub fn body(&self) -> Markup {
        self.0.cast_first_child().expect("strong node is missing markup body")
    }
}

node! {
    /// Emphasized content: `_Emphasized_`.
    Emph
}

impl Emph {
    /// The contents of the emphasis node.
    pub fn body(&self) -> Markup {
        self.0
            .cast_first_child()
            .expect("emphasis node is missing markup body")
    }
}

node! {
    /// Raw text with optional syntax highlighting: `` `...` ``.
    Raw
}

impl Raw {
    /// The raw text.
    pub fn text(&self) -> &EcoString {
        &self.get().text
    }

    /// An optional identifier specifying the language to syntax-highlight in.
    pub fn lang(&self) -> Option<&EcoString> {
        self.get().lang.as_ref()
    }

    /// Whether the raw block is block-level.
    pub fn block(&self) -> bool {
        self.get().block
    }

    /// The raw fields.
    fn get(&self) -> &RawFields {
        match self.0.kind() {
            NodeKind::Raw(v) => v.as_ref(),
            _ => panic!("raw is of wrong kind"),
        }
    }
}

node! {
    /// A hyperlink: `https://typst.org`.
    Link
}

impl Link {
    /// Get the URL.
    pub fn url(&self) -> &EcoString {
        match self.0.kind() {
            NodeKind::Link(url) => url,
            _ => panic!("link is of wrong kind"),
        }
    }
}

node! {
    /// A label: `<label>`.
    Label
}

impl Label {
    /// Get the label.
    pub fn get(&self) -> &EcoString {
        match self.0.kind() {
            NodeKind::Label(v) => v,
            _ => panic!("label is of wrong kind"),
        }
    }
}

node! {
    /// A reference: `@target`.
    Ref
}

impl Ref {
    /// Get the target.
    pub fn get(&self) -> &EcoString {
        match self.0.kind() {
            NodeKind::Ref(v) => v,
            _ => panic!("reference is of wrong kind"),
        }
    }
}

node! {
    /// A section heading: `= Introduction`.
    Heading
}

impl Heading {
    /// The contents of the heading.
    pub fn body(&self) -> Markup {
        self.0.cast_first_child().expect("heading is missing markup body")
    }

    /// The section depth (numer of equals signs).
    pub fn level(&self) -> NonZeroUsize {
        self.0
            .children()
            .filter(|n| n.kind() == &NodeKind::Eq)
            .count()
            .try_into()
            .expect("heading is missing equals sign")
    }
}

node! {
    /// An item in an unordered list: `- ...`.
    ListItem
}

impl ListItem {
    /// The contents of the list item.
    pub fn body(&self) -> Markup {
        self.0.cast_first_child().expect("list item is missing body")
    }
}

node! {
    /// An item in an enumeration (ordered list): `+ ...` or `1. ...`.
    EnumItem
}

impl EnumItem {
    /// The explicit numbering, if any: `23.`.
    pub fn number(&self) -> Option<usize> {
        self.0.children().find_map(|node| match node.kind() {
            NodeKind::EnumNumbering(num) => Some(*num),
            _ => None,
        })
    }

    /// The contents of the list item.
    pub fn body(&self) -> Markup {
        self.0.cast_first_child().expect("enum item is missing body")
    }
}

node! {
    /// An item in a description list: `/ Term: Details`.
    DescItem
}

impl DescItem {
    /// The term described by the item.
    pub fn term(&self) -> Markup {
        self.0
            .cast_first_child()
            .expect("description list item is missing term")
    }

    /// The description of the term.
    pub fn body(&self) -> Markup {
        self.0
            .cast_last_child()
            .expect("description list item is missing body")
    }
}

node! {
    /// A math formula: `$x$`, `$ x^2 $`.
    Math
}

impl Math {
    /// The children.
    pub fn children(&self) -> impl DoubleEndedIterator<Item = MathNode> + '_ {
        self.0.children().filter_map(SyntaxNode::cast)
    }

    /// Whether this is a display-level math formula.
    pub fn display(&self) -> bool {
        matches!(self.children().next(), Some(MathNode::Space(_)))
            && matches!(self.children().last(), Some(MathNode::Space(_)))
    }
}

/// A single piece of a math formula.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum MathNode {
    /// Whitespace.
    Space(Space),
    /// A forced line break: `\`.
    Linebreak(Linebreak),
    /// An escape sequence: `\#`, `\u{1F5FA}`.
    Escape(Escape),
    /// An atom: `x`, `+`, `12`.
    Atom(Atom),
    /// A base with optional sub- and superscripts: `a_1^2`.
    Script(Script),
    /// A fraction: `x/2`.
    Frac(Frac),
    /// An alignment indicator: `&`, `&&`.
    Align(Align),
    /// Grouped mathematical material.
    Group(Math),
    /// An expression.
    Expr(Expr),
}

impl TypedNode for MathNode {
    fn from_untyped(node: &SyntaxNode) -> Option<Self> {
        match node.kind() {
            NodeKind::Space { .. } => node.cast().map(Self::Space),
            NodeKind::Linebreak => node.cast().map(Self::Linebreak),
            NodeKind::Escape(_) => node.cast().map(Self::Escape),
            NodeKind::Atom(_) => node.cast().map(Self::Atom),
            NodeKind::Script => node.cast().map(Self::Script),
            NodeKind::Frac => node.cast().map(Self::Frac),
            NodeKind::Align => node.cast().map(Self::Align),
            NodeKind::Math => node.cast().map(Self::Group),
            _ => node.cast().map(Self::Expr),
        }
    }

    fn as_untyped(&self) -> &SyntaxNode {
        match self {
            Self::Space(v) => v.as_untyped(),
            Self::Linebreak(v) => v.as_untyped(),
            Self::Escape(v) => v.as_untyped(),
            Self::Atom(v) => v.as_untyped(),
            Self::Script(v) => v.as_untyped(),
            Self::Frac(v) => v.as_untyped(),
            Self::Align(v) => v.as_untyped(),
            Self::Group(v) => v.as_untyped(),
            Self::Expr(v) => v.as_untyped(),
        }
    }
}

node! {
    /// A atom in a formula: `x`, `+`, `12`.
    Atom
}

impl Atom {
    /// Get the atom's text.
    pub fn get(&self) -> &EcoString {
        match self.0.kind() {
            NodeKind::Atom(v) => v,
            _ => panic!("atom is of wrong kind"),
        }
    }
}

node! {
    /// A base with an optional sub- and superscript in a formula: `a_1^2`.
    Script
}

impl Script {
    /// The base of the script.
    pub fn base(&self) -> MathNode {
        self.0.cast_first_child().expect("script node is missing base")
    }

    /// The subscript.
    pub fn sub(&self) -> Option<MathNode> {
        self.0
            .children()
            .skip_while(|node| !matches!(node.kind(), NodeKind::Underscore))
            .nth(1)
            .map(|node| node.cast().expect("script node has invalid subscript"))
    }

    /// The superscript.
    pub fn sup(&self) -> Option<MathNode> {
        self.0
            .children()
            .skip_while(|node| !matches!(node.kind(), NodeKind::Hat))
            .nth(1)
            .map(|node| node.cast().expect("script node has invalid superscript"))
    }
}

node! {
    /// A fraction in a formula: `x/2`
    Frac
}

impl Frac {
    /// The numerator.
    pub fn num(&self) -> MathNode {
        self.0.cast_first_child().expect("fraction is missing numerator")
    }

    /// The denominator.
    pub fn denom(&self) -> MathNode {
        self.0.cast_last_child().expect("fraction is missing denominator")
    }
}

node! {
    /// An alignment indicator in a formula: `&`, `&&`.
    Align
}

impl Align {
    /// The number of ampersands.
    pub fn count(&self) -> usize {
        self.0.children().filter(|n| n.kind() == &NodeKind::Amp).count()
    }
}

/// An expression.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Expr {
    /// A literal: `1`, `true`, ...
    Lit(Lit),
    /// An identifier: `left`.
    Ident(Ident),
    /// A code block: `{ let x = 1; x + 2 }`.
    Code(CodeBlock),
    /// A content block: `[*Hi* there!]`.
    Content(ContentBlock),
    /// A grouped expression: `(1 + 2)`.
    Parenthesized(Parenthesized),
    /// An array: `(1, "hi", 12cm)`.
    Array(Array),
    /// A dictionary: `(thickness: 3pt, pattern: dashed)`.
    Dict(Dict),
    /// A unary operation: `-x`.
    Unary(Unary),
    /// A binary operation: `a + b`.
    Binary(Binary),
    /// A field access: `properties.age`.
    FieldAccess(FieldAccess),
    /// An invocation of a function: `f(x, y)`.
    FuncCall(FuncCall),
    /// An invocation of a method: `array.push(v)`.
    MethodCall(MethodCall),
    /// A closure: `(x, y) => z`.
    Closure(Closure),
    /// A let binding: `let x = 1`.
    Let(LetBinding),
    /// A set rule: `set text(...)`.
    Set(SetRule),
    /// A show rule: `show heading: it => [*{it.body}*]`.
    Show(ShowRule),
    /// An if-else conditional: `if x { y } else { z }`.
    Conditional(Conditional),
    /// A while loop: `while x { y }`.
    While(WhileLoop),
    /// A for loop: `for x in y { z }`.
    For(ForLoop),
    /// A module import: `import a, b, c from "utils.typ"`.
    Import(ModuleImport),
    /// A module include: `include "chapter1.typ"`.
    Include(ModuleInclude),
    /// A break from a loop: `break`.
    Break(LoopBreak),
    /// A continue in a loop: `continue`.
    Continue(LoopContinue),
    /// A return from a function: `return`, `return x + 1`.
    Return(FuncReturn),
}

impl TypedNode for Expr {
    fn from_untyped(node: &SyntaxNode) -> Option<Self> {
        match node.kind() {
            NodeKind::Ident(_) => node.cast().map(Self::Ident),
            NodeKind::CodeBlock => node.cast().map(Self::Code),
            NodeKind::ContentBlock => node.cast().map(Self::Content),
            NodeKind::Parenthesized => node.cast().map(Self::Parenthesized),
            NodeKind::Array => node.cast().map(Self::Array),
            NodeKind::Dict => node.cast().map(Self::Dict),
            NodeKind::Unary => node.cast().map(Self::Unary),
            NodeKind::Binary => node.cast().map(Self::Binary),
            NodeKind::FieldAccess => node.cast().map(Self::FieldAccess),
            NodeKind::FuncCall => node.cast().map(Self::FuncCall),
            NodeKind::MethodCall => node.cast().map(Self::MethodCall),
            NodeKind::Closure => node.cast().map(Self::Closure),
            NodeKind::LetBinding => node.cast().map(Self::Let),
            NodeKind::SetRule => node.cast().map(Self::Set),
            NodeKind::ShowRule => node.cast().map(Self::Show),
            NodeKind::Conditional => node.cast().map(Self::Conditional),
            NodeKind::WhileLoop => node.cast().map(Self::While),
            NodeKind::ForLoop => node.cast().map(Self::For),
            NodeKind::ModuleImport => node.cast().map(Self::Import),
            NodeKind::ModuleInclude => node.cast().map(Self::Include),
            NodeKind::LoopBreak => node.cast().map(Self::Break),
            NodeKind::LoopContinue => node.cast().map(Self::Continue),
            NodeKind::FuncReturn => node.cast().map(Self::Return),
            _ => node.cast().map(Self::Lit),
        }
    }

    fn as_untyped(&self) -> &SyntaxNode {
        match self {
            Self::Lit(v) => v.as_untyped(),
            Self::Code(v) => v.as_untyped(),
            Self::Content(v) => v.as_untyped(),
            Self::Ident(v) => v.as_untyped(),
            Self::Array(v) => v.as_untyped(),
            Self::Dict(v) => v.as_untyped(),
            Self::Parenthesized(v) => v.as_untyped(),
            Self::Unary(v) => v.as_untyped(),
            Self::Binary(v) => v.as_untyped(),
            Self::FieldAccess(v) => v.as_untyped(),
            Self::FuncCall(v) => v.as_untyped(),
            Self::MethodCall(v) => v.as_untyped(),
            Self::Closure(v) => v.as_untyped(),
            Self::Let(v) => v.as_untyped(),
            Self::Set(v) => v.as_untyped(),
            Self::Show(v) => v.as_untyped(),
            Self::Conditional(v) => v.as_untyped(),
            Self::While(v) => v.as_untyped(),
            Self::For(v) => v.as_untyped(),
            Self::Import(v) => v.as_untyped(),
            Self::Include(v) => v.as_untyped(),
            Self::Break(v) => v.as_untyped(),
            Self::Continue(v) => v.as_untyped(),
            Self::Return(v) => v.as_untyped(),
        }
    }
}

impl Expr {
    /// Whether the expression can be shortened in markup with a hashtag.
    pub fn has_short_form(&self) -> bool {
        matches!(
            self,
            Self::Ident(_)
                | Self::FuncCall(_)
                | Self::Let(_)
                | Self::Set(_)
                | Self::Show(_)
                | Self::Conditional(_)
                | Self::While(_)
                | Self::For(_)
                | Self::Import(_)
                | Self::Include(_)
        )
    }
}

node! {
    /// A literal: `1`, `true`, ...
    Lit: NodeKind::None
       | NodeKind::Auto
       | NodeKind::Bool(_)
       | NodeKind::Int(_)
       | NodeKind::Float(_)
       | NodeKind::Numeric(_, _)
       | NodeKind::Str(_)
}

impl Lit {
    /// The kind of literal.
    pub fn kind(&self) -> LitKind {
        match *self.0.kind() {
            NodeKind::None => LitKind::None,
            NodeKind::Auto => LitKind::Auto,
            NodeKind::Bool(v) => LitKind::Bool(v),
            NodeKind::Int(v) => LitKind::Int(v),
            NodeKind::Float(v) => LitKind::Float(v),
            NodeKind::Numeric(v, unit) => LitKind::Numeric(v, unit),
            NodeKind::Str(ref v) => LitKind::Str(v.clone()),
            _ => panic!("literal is of wrong kind"),
        }
    }
}

/// The kind of a literal.
#[derive(Debug, Clone, PartialEq)]
pub enum LitKind {
    /// The none literal: `none`.
    None,
    /// The auto literal: `auto`.
    Auto,
    /// A boolean: `true`, `false`.
    Bool(bool),
    /// An integer: `120`.
    Int(i64),
    /// A floating-point number: `1.2`, `10e-4`.
    Float(f64),
    /// A numeric value with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`.
    Numeric(f64, Unit),
    /// A quoted string: `"..."`.
    Str(EcoString),
}

node! {
    /// A code block: `{ let x = 1; x + 2 }`.
    CodeBlock
}

impl CodeBlock {
    /// The list of expressions contained in the block.
    pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
        self.0.children().filter_map(SyntaxNode::cast)
    }
}

node! {
    /// A content block: `[*Hi* there!]`.
    ContentBlock
}

impl ContentBlock {
    /// The contained markup.
    pub fn body(&self) -> Markup {
        self.0.cast_first_child().expect("content block is missing body")
    }
}

node! {
    /// A grouped expression: `(1 + 2)`.
    Parenthesized
}

impl Parenthesized {
    /// The wrapped expression.
    pub fn expr(&self) -> Expr {
        self.0
            .cast_first_child()
            .expect("parenthesized expression is missing expression")
    }
}

node! {
    /// An array: `(1, "hi", 12cm)`.
    Array
}

impl Array {
    /// The array's items.
    pub fn items(&self) -> impl DoubleEndedIterator<Item = ArrayItem> + '_ {
        self.0.children().filter_map(SyntaxNode::cast)
    }
}

/// An item in an array.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum ArrayItem {
    /// A bare expression: `12`.
    Pos(Expr),
    /// A spreaded expression: `..things`.
    Spread(Expr),
}

impl TypedNode for ArrayItem {
    fn from_untyped(node: &SyntaxNode) -> Option<Self> {
        match node.kind() {
            NodeKind::Spread => node.cast_first_child().map(Self::Spread),
            _ => node.cast().map(Self::Pos),
        }
    }

    fn as_untyped(&self) -> &SyntaxNode {
        match self {
            Self::Pos(v) => v.as_untyped(),
            Self::Spread(v) => v.as_untyped(),
        }
    }
}

node! {
    /// A dictionary: `(thickness: 3pt, pattern: dashed)`.
    Dict
}

impl Dict {
    /// The dictionary's items.
    pub fn items(&self) -> impl DoubleEndedIterator<Item = DictItem> + '_ {
        self.0.children().filter_map(SyntaxNode::cast)
    }
}

/// An item in an dictionary expresssion.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum DictItem {
    /// A named pair: `thickness: 3pt`.
    Named(Named),
    /// A keyed pair: `"spacy key": true`.
    Keyed(Keyed),
    /// A spreaded expression: `..things`.
    Spread(Expr),
}

impl TypedNode for DictItem {
    fn from_untyped(node: &SyntaxNode) -> Option<Self> {
        match node.kind() {
            NodeKind::Named => node.cast().map(Self::Named),
            NodeKind::Keyed => node.cast().map(Self::Keyed),
            NodeKind::Spread => node.cast_first_child().map(Self::Spread),
            _ => None,
        }
    }

    fn as_untyped(&self) -> &SyntaxNode {
        match self {
            Self::Named(v) => v.as_untyped(),
            Self::Keyed(v) => v.as_untyped(),
            Self::Spread(v) => v.as_untyped(),
        }
    }
}

node! {
    /// A named pair: `thickness: 3pt`.
    Named
}

impl Named {
    /// The name: `thickness`.
    pub fn name(&self) -> Ident {
        self.0.cast_first_child().expect("named pair is missing name")
    }

    /// The right-hand side of the pair: `3pt`.
    pub fn expr(&self) -> Expr {
        self.0.cast_last_child().expect("named pair is missing expression")
    }
}

node! {
    /// A keyed pair: `"spacy key": true`.
    Keyed
}

impl Keyed {
    /// The key: `"spacy key"`.
    pub fn key(&self) -> EcoString {
        self.0
            .children()
            .find_map(|node| match node.kind() {
                NodeKind::Str(key) => Some(key.clone()),
                _ => None,
            })
            .expect("keyed pair is missing key")
    }

    /// The right-hand side of the pair: `true`.
    pub fn expr(&self) -> Expr {
        self.0.cast_last_child().expect("keyed pair is missing expression")
    }
}

node! {
    /// A unary operation: `-x`.
    Unary
}

impl Unary {
    /// The operator: `-`.
    pub fn op(&self) -> UnOp {
        self.0
            .children()
            .find_map(|node| UnOp::from_token(node.kind()))
            .expect("unary operation is missing operator")
    }

    /// The expression to operate on: `x`.
    pub fn expr(&self) -> Expr {
        self.0.cast_last_child().expect("unary operation is missing child")
    }
}

/// A unary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum UnOp {
    /// The plus operator: `+`.
    Pos,
    /// The negation operator: `-`.
    Neg,
    /// The boolean `not`.
    Not,
}

impl UnOp {
    /// Try to convert the token into a unary operation.
    pub fn from_token(token: &NodeKind) -> Option<Self> {
        Some(match token {
            NodeKind::Plus => Self::Pos,
            NodeKind::Minus => Self::Neg,
            NodeKind::Not => Self::Not,
            _ => return None,
        })
    }

    /// The precedence of this operator.
    pub fn precedence(self) -> usize {
        match self {
            Self::Pos | Self::Neg => 7,
            Self::Not => 4,
        }
    }

    /// The string representation of this operation.
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Pos => "+",
            Self::Neg => "-",
            Self::Not => "not",
        }
    }
}

node! {
    /// A binary operation: `a + b`.
    Binary
}

impl Binary {
    /// The binary operator: `+`.
    pub fn op(&self) -> BinOp {
        let mut not = false;
        self.0
            .children()
            .find_map(|node| match node.kind() {
                NodeKind::Not => {
                    not = true;
                    None
                }
                NodeKind::In if not => Some(BinOp::NotIn),
                _ => BinOp::from_token(node.kind()),
            })
            .expect("binary operation is missing operator")
    }

    /// The left-hand side of the operation: `a`.
    pub fn lhs(&self) -> Expr {
        self.0
            .cast_first_child()
            .expect("binary operation is missing left-hand side")
    }

    /// The right-hand side of the operation: `b`.
    pub fn rhs(&self) -> Expr {
        self.0
            .cast_last_child()
            .expect("binary operation is missing right-hand side")
    }
}

/// A binary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum BinOp {
    /// The addition operator: `+`.
    Add,
    /// The subtraction operator: `-`.
    Sub,
    /// The multiplication operator: `*`.
    Mul,
    /// The division operator: `/`.
    Div,
    /// The short-circuiting boolean `and`.
    And,
    /// The short-circuiting boolean `or`.
    Or,
    /// The equality operator: `==`.
    Eq,
    /// The inequality operator: `!=`.
    Neq,
    /// The less-than operator: `<`.
    Lt,
    /// The less-than or equal operator: `<=`.
    Leq,
    /// The greater-than operator: `>`.
    Gt,
    /// The greater-than or equal operator: `>=`.
    Geq,
    /// The assignment operator: `=`.
    Assign,
    /// The containment operator: `in`.
    In,
    /// The inversed containment operator: `not in`.
    NotIn,
    /// The add-assign operator: `+=`.
    AddAssign,
    /// The subtract-assign oeprator: `-=`.
    SubAssign,
    /// The multiply-assign operator: `*=`.
    MulAssign,
    /// The divide-assign operator: `/=`.
    DivAssign,
}

impl BinOp {
    /// Try to convert the token into a binary operation.
    pub fn from_token(token: &NodeKind) -> Option<Self> {
        Some(match token {
            NodeKind::Plus => Self::Add,
            NodeKind::Minus => Self::Sub,
            NodeKind::Star => Self::Mul,
            NodeKind::Slash => Self::Div,
            NodeKind::And => Self::And,
            NodeKind::Or => Self::Or,
            NodeKind::EqEq => Self::Eq,
            NodeKind::ExclEq => Self::Neq,
            NodeKind::Lt => Self::Lt,
            NodeKind::LtEq => Self::Leq,
            NodeKind::Gt => Self::Gt,
            NodeKind::GtEq => Self::Geq,
            NodeKind::Eq => Self::Assign,
            NodeKind::In => Self::In,
            NodeKind::PlusEq => Self::AddAssign,
            NodeKind::HyphEq => Self::SubAssign,
            NodeKind::StarEq => Self::MulAssign,
            NodeKind::SlashEq => Self::DivAssign,
            _ => return None,
        })
    }

    /// The precedence of this operator.
    pub fn precedence(self) -> usize {
        match self {
            Self::Mul => 6,
            Self::Div => 6,
            Self::Add => 5,
            Self::Sub => 5,
            Self::Eq => 4,
            Self::Neq => 4,
            Self::Lt => 4,
            Self::Leq => 4,
            Self::Gt => 4,
            Self::Geq => 4,
            Self::In => 4,
            Self::NotIn => 4,
            Self::And => 3,
            Self::Or => 2,
            Self::Assign => 1,
            Self::AddAssign => 1,
            Self::SubAssign => 1,
            Self::MulAssign => 1,
            Self::DivAssign => 1,
        }
    }

    /// The associativity of this operator.
    pub fn assoc(self) -> Assoc {
        match self {
            Self::Add => Assoc::Left,
            Self::Sub => Assoc::Left,
            Self::Mul => Assoc::Left,
            Self::Div => Assoc::Left,
            Self::And => Assoc::Left,
            Self::Or => Assoc::Left,
            Self::Eq => Assoc::Left,
            Self::Neq => Assoc::Left,
            Self::Lt => Assoc::Left,
            Self::Leq => Assoc::Left,
            Self::Gt => Assoc::Left,
            Self::Geq => Assoc::Left,
            Self::In => Assoc::Left,
            Self::NotIn => Assoc::Left,
            Self::Assign => Assoc::Right,
            Self::AddAssign => Assoc::Right,
            Self::SubAssign => Assoc::Right,
            Self::MulAssign => Assoc::Right,
            Self::DivAssign => Assoc::Right,
        }
    }

    /// The string representation of this operation.
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Add => "+",
            Self::Sub => "-",
            Self::Mul => "*",
            Self::Div => "/",
            Self::And => "and",
            Self::Or => "or",
            Self::Eq => "==",
            Self::Neq => "!=",
            Self::Lt => "<",
            Self::Leq => "<=",
            Self::Gt => ">",
            Self::Geq => ">=",
            Self::In => "in",
            Self::NotIn => "not in",
            Self::Assign => "=",
            Self::AddAssign => "+=",
            Self::SubAssign => "-=",
            Self::MulAssign => "*=",
            Self::DivAssign => "/=",
        }
    }
}

/// The associativity of a binary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Assoc {
    /// Left-associative: `a + b + c` is equivalent to `(a + b) + c`.
    Left,
    /// Right-associative: `a = b = c` is equivalent to `a = (b = c)`.
    Right,
}

node! {
    /// A field access: `properties.age`.
    FieldAccess
}

impl FieldAccess {
    /// The expression to access the field on.
    pub fn target(&self) -> Expr {
        self.0.cast_first_child().expect("field access is missing object")
    }

    /// The name of the field.
    pub fn field(&self) -> Ident {
        self.0.cast_last_child().expect("field access is missing name")
    }
}

node! {
    /// An invocation of a function: `f(x, y)`.
    FuncCall
}

impl FuncCall {
    /// The function to call.
    pub fn callee(&self) -> Expr {
        self.0.cast_first_child().expect("function call is missing callee")
    }

    /// The arguments to the function.
    pub fn args(&self) -> Args {
        self.0
            .cast_last_child()
            .expect("function call is missing argument list")
    }
}

node! {
    /// An invocation of a method: `array.push(v)`.
    MethodCall
}

impl MethodCall {
    /// The expresion to call the method on.
    pub fn target(&self) -> Expr {
        self.0.cast_first_child().expect("method call is missing target")
    }

    /// The name of the method.
    pub fn method(&self) -> Ident {
        self.0.cast_last_child().expect("method call is missing name")
    }

    /// The arguments to the method.
    pub fn args(&self) -> Args {
        self.0
            .cast_last_child()
            .expect("method call is missing argument list")
    }
}

node! {
    /// A function call's argument list: `(12pt, y)`.
    Args
}

impl Args {
    /// The positional and named arguments.
    pub fn items(&self) -> impl DoubleEndedIterator<Item = Arg> + '_ {
        self.0.children().filter_map(SyntaxNode::cast)
    }
}

/// An argument to a function call.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Arg {
    /// A positional argument: `12`.
    Pos(Expr),
    /// A named argument: `draw: false`.
    Named(Named),
    /// A spreaded argument: `..things`.
    Spread(Expr),
}

impl TypedNode for Arg {
    fn from_untyped(node: &SyntaxNode) -> Option<Self> {
        match node.kind() {
            NodeKind::Named => node.cast().map(Self::Named),
            NodeKind::Spread => node.cast_first_child().map(Self::Spread),
            _ => node.cast().map(Self::Pos),
        }
    }

    fn as_untyped(&self) -> &SyntaxNode {
        match self {
            Self::Pos(v) => v.as_untyped(),
            Self::Named(v) => v.as_untyped(),
            Self::Spread(v) => v.as_untyped(),
        }
    }
}

node! {
    /// A closure: `(x, y) => z`.
    Closure
}

impl Closure {
    /// The name of the closure.
    ///
    /// This only exists if you use the function syntax sugar: `let f(x) = y`.
    pub fn name(&self) -> Option<Ident> {
        self.0.cast_first_child()
    }

    /// The parameter bindings.
    pub fn params(&self) -> impl DoubleEndedIterator<Item = Param> + '_ {
        self.0
            .children()
            .find(|x| x.kind() == &NodeKind::Params)
            .expect("closure is missing parameter list")
            .children()
            .filter_map(SyntaxNode::cast)
    }

    /// The body of the closure.
    pub fn body(&self) -> Expr {
        self.0.cast_last_child().expect("closure is missing body")
    }
}

/// A parameter to a closure.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Param {
    /// A positional parameter: `x`.
    Pos(Ident),
    /// A named parameter with a default value: `draw: false`.
    Named(Named),
    /// An argument sink: `..args`.
    Sink(Ident),
}

impl TypedNode for Param {
    fn from_untyped(node: &SyntaxNode) -> Option<Self> {
        match node.kind() {
            NodeKind::Ident(_) => node.cast().map(Self::Pos),
            NodeKind::Named => node.cast().map(Self::Named),
            NodeKind::Spread => node.cast_first_child().map(Self::Sink),
            _ => None,
        }
    }

    fn as_untyped(&self) -> &SyntaxNode {
        match self {
            Self::Pos(v) => v.as_untyped(),
            Self::Named(v) => v.as_untyped(),
            Self::Sink(v) => v.as_untyped(),
        }
    }
}

node! {
    /// A let binding: `let x = 1`.
    LetBinding
}

impl LetBinding {
    /// The binding to assign to.
    pub fn binding(&self) -> Ident {
        match self.0.cast_first_child() {
            Some(Expr::Ident(binding)) => binding,
            Some(Expr::Closure(closure)) => {
                closure.name().expect("let-bound closure is missing name")
            }
            _ => panic!("let is missing binding"),
        }
    }

    /// The expression the binding is initialized with.
    pub fn init(&self) -> Option<Expr> {
        if self.0.cast_first_child::<Ident>().is_some() {
            // This is a normal binding like `let x = 1`.
            self.0.children().filter_map(SyntaxNode::cast).nth(1)
        } else {
            // This is a closure binding like `let f(x) = 1`.
            self.0.cast_first_child()
        }
    }
}

node! {
    /// A set rule: `set text(...)`.
    SetRule
}

impl SetRule {
    /// The function to set style properties for.
    pub fn target(&self) -> Ident {
        self.0.cast_first_child().expect("set rule is missing target")
    }

    /// The style properties to set.
    pub fn args(&self) -> Args {
        self.0.cast_last_child().expect("set rule is missing argument list")
    }

    /// A condition under which the set rule applies.
    pub fn condition(&self) -> Option<Expr> {
        self.0
            .children()
            .skip_while(|child| child.kind() != &NodeKind::If)
            .find_map(SyntaxNode::cast)
    }
}

node! {
    /// A show rule: `show heading: it => [*{it.body}*]`.
    ShowRule
}

impl ShowRule {
    /// Defines which nodes the show rule applies to.
    pub fn selector(&self) -> Option<Expr> {
        self.0
            .children()
            .rev()
            .skip_while(|child| child.kind() != &NodeKind::Colon)
            .find_map(SyntaxNode::cast)
    }

    /// The transformation recipe.
    pub fn transform(&self) -> Expr {
        self.0.cast_last_child().expect("show rule is missing transform")
    }
}

node! {
    /// An if-else conditional: `if x { y } else { z }`.
    Conditional
}

impl Conditional {
    /// The condition which selects the body to evaluate.
    pub fn condition(&self) -> Expr {
        self.0.cast_first_child().expect("conditional is missing condition")
    }

    /// The expression to evaluate if the condition is true.
    pub fn if_body(&self) -> Expr {
        self.0
            .children()
            .filter_map(SyntaxNode::cast)
            .nth(1)
            .expect("conditional is missing body")
    }

    /// The expression to evaluate if the condition is false.
    pub fn else_body(&self) -> Option<Expr> {
        self.0.children().filter_map(SyntaxNode::cast).nth(2)
    }
}

node! {
    /// A while loop: `while x { y }`.
    WhileLoop
}

impl WhileLoop {
    /// The condition which selects whether to evaluate the body.
    pub fn condition(&self) -> Expr {
        self.0.cast_first_child().expect("while loop is missing condition")
    }

    /// The expression to evaluate while the condition is true.
    pub fn body(&self) -> Expr {
        self.0.cast_last_child().expect("while loop is missing body")
    }
}

node! {
    /// A for loop: `for x in y { z }`.
    ForLoop
}

impl ForLoop {
    /// The pattern to assign to.
    pub fn pattern(&self) -> ForPattern {
        self.0.cast_first_child().expect("for loop is missing pattern")
    }

    /// The expression to iterate over.
    pub fn iter(&self) -> Expr {
        self.0.cast_first_child().expect("for loop is missing iterable")
    }

    /// The expression to evaluate for each iteration.
    pub fn body(&self) -> Expr {
        self.0.cast_last_child().expect("for loop is missing body")
    }
}

node! {
    /// A for loop's destructuring pattern: `x` or `x, y`.
    ForPattern
}

impl ForPattern {
    /// The key part of the pattern: index for arrays, name for dictionaries.
    pub fn key(&self) -> Option<Ident> {
        let mut children = self.0.children().filter_map(SyntaxNode::cast);
        let key = children.next();
        if children.next().is_some() {
            key
        } else {
            None
        }
    }

    /// The value part of the pattern.
    pub fn value(&self) -> Ident {
        self.0.cast_last_child().expect("for loop pattern is missing value")
    }
}

node! {
    /// A module import: `import a, b, c from "utils.typ"`.
    ModuleImport
}

impl ModuleImport {
    /// The items to be imported.
    pub fn imports(&self) -> Imports {
        self.0
            .children()
            .find_map(|node| match node.kind() {
                NodeKind::Star => Some(Imports::Wildcard),
                NodeKind::ImportItems => {
                    let items = node.children().filter_map(SyntaxNode::cast).collect();
                    Some(Imports::Items(items))
                }
                _ => None,
            })
            .expect("module import is missing items")
    }

    /// The path to the file that should be imported.
    pub fn path(&self) -> Expr {
        self.0.cast_last_child().expect("module import is missing path")
    }
}

/// The items that ought to be imported from a file.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Imports {
    /// All items in the scope of the file should be imported.
    Wildcard,
    /// The specified items from the file should be imported.
    Items(Vec<Ident>),
}

node! {
    /// A module include: `include "chapter1.typ"`.
    ModuleInclude
}

impl ModuleInclude {
    /// The path to the file that should be included.
    pub fn path(&self) -> Expr {
        self.0.cast_last_child().expect("module include is missing path")
    }
}

node! {
    /// A break from a loop: `break`.
    LoopBreak
}

node! {
    /// A continue in a loop: `continue`.
    LoopContinue
}

node! {
    /// A return from a function: `return`, `return x + 1`.
    FuncReturn
}

impl FuncReturn {
    /// The expression to return.
    pub fn body(&self) -> Option<Expr> {
        self.0.cast_last_child()
    }
}

node! {
    /// An identifier: `it`.
    Ident
}

impl Ident {
    /// Get the identifier.
    pub fn get(&self) -> &EcoString {
        match self.0.kind() {
            NodeKind::Ident(id) => id,
            _ => panic!("identifier is of wrong kind"),
        }
    }

    /// Take out the container identifier.
    pub fn take(self) -> EcoString {
        match self.0 {
            SyntaxNode::Leaf(NodeData { kind: NodeKind::Ident(id), .. }) => id,
            _ => panic!("identifier is of wrong kind"),
        }
    }
}

impl Deref for Ident {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        self.get()
    }
}
