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

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

use unscanny::Scanner;

use super::{
    is_id_continue, is_id_start, is_newline, split_newlines, Span, SyntaxKind, SyntaxNode,
};
use crate::geom::{AbsUnit, AngleUnit};
use crate::util::EcoString;

/// A typed AST node.
pub trait AstNode: 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) => {
        #[derive(Debug, Default, Clone, PartialEq, Hash)]
        #[repr(transparent)]
        $(#[$attr])*
        pub struct $name(SyntaxNode);

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

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

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

impl Markup {
    /// The expressions.
    pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
        let mut was_stmt = false;
        self.0
            .children()
            .filter(move |node| {
                // Ignore newline directly after statements without semicolons.
                let kind = node.kind();
                let keep = !was_stmt || node.kind() != SyntaxKind::Space;
                was_stmt = kind.is_stmt();
                keep
            })
            .filter_map(Expr::cast_with_space)
    }
}

/// An expression in markup, math or code.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Expr {
    /// Plain text without markup.
    Text(Text),
    /// Whitespace in markup or math. Has at most one newline in markup, as more
    /// indicate a paragraph break.
    Space(Space),
    /// A forced line break: `\`.
    Linebreak(Linebreak),
    /// A paragraph break, indicated by one or multiple blank lines.
    Parbreak(Parbreak),
    /// 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: `<intro>`.
    Label(Label),
    /// A reference: `@target`.
    Ref(Ref),
    /// A section heading: `= Introduction`.
    Heading(Heading),
    /// An item in a bullet list: `- ...`.
    List(ListItem),
    /// An item in an enumeration (numbered list): `+ ...` or `1. ...`.
    Enum(EnumItem),
    /// An item in a term list: `/ Term: Details`.
    Term(TermItem),
    /// A math formula: `$x$`, `$ x^2 $`.
    Formula(Formula),
    /// A math formula: `$x$`, `$ x^2 $`.
    Math(Math),
    /// An identifier in a math formula: `pi`.
    MathIdent(MathIdent),
    /// An alignment point in a math formula: `&`.
    MathAlignPoint(MathAlignPoint),
    /// Matched delimiters surrounding math in a formula: `[x + y]`.
    MathDelimited(MathDelimited),
    /// A base with optional attachments in a formula: `a_1^2`.
    MathAttach(MathAttach),
    /// A fraction in a math formula: `x/2`.
    MathFrac(MathFrac),
    /// An identifier: `left`.
    Ident(Ident),
    /// The `none` literal.
    None(None),
    /// The `auto` literal.
    Auto(Auto),
    /// A boolean: `true`, `false`.
    Bool(Bool),
    /// An integer: `120`.
    Int(Int),
    /// A floating-point number: `1.2`, `10e-4`.
    Float(Float),
    /// A numeric value with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`.
    Numeric(Numeric),
    /// A quoted string: `"..."`.
    Str(Str),
    /// 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 or method: `f(x, y)`.
    FuncCall(FuncCall),
    /// 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 Expr {
    fn cast_with_space(node: &SyntaxNode) -> Option<Self> {
        match node.kind() {
            SyntaxKind::Space => node.cast().map(Self::Space),
            _ => Self::from_untyped(node),
        }
    }
}

impl AstNode for Expr {
    fn from_untyped(node: &SyntaxNode) -> Option<Self> {
        match node.kind() {
            SyntaxKind::Linebreak => node.cast().map(Self::Linebreak),
            SyntaxKind::Parbreak => node.cast().map(Self::Parbreak),
            SyntaxKind::Text => node.cast().map(Self::Text),
            SyntaxKind::Escape => node.cast().map(Self::Escape),
            SyntaxKind::Shorthand => node.cast().map(Self::Shorthand),
            SyntaxKind::SmartQuote => node.cast().map(Self::SmartQuote),
            SyntaxKind::Strong => node.cast().map(Self::Strong),
            SyntaxKind::Emph => node.cast().map(Self::Emph),
            SyntaxKind::Raw => node.cast().map(Self::Raw),
            SyntaxKind::Link => node.cast().map(Self::Link),
            SyntaxKind::Label => node.cast().map(Self::Label),
            SyntaxKind::Ref => node.cast().map(Self::Ref),
            SyntaxKind::Heading => node.cast().map(Self::Heading),
            SyntaxKind::ListItem => node.cast().map(Self::List),
            SyntaxKind::EnumItem => node.cast().map(Self::Enum),
            SyntaxKind::TermItem => node.cast().map(Self::Term),
            SyntaxKind::Formula => node.cast().map(Self::Formula),
            SyntaxKind::Math => node.cast().map(Self::Math),
            SyntaxKind::MathIdent => node.cast().map(Self::MathIdent),
            SyntaxKind::MathAlignPoint => node.cast().map(Self::MathAlignPoint),
            SyntaxKind::MathDelimited => node.cast().map(Self::MathDelimited),
            SyntaxKind::MathAttach => node.cast().map(Self::MathAttach),
            SyntaxKind::MathFrac => node.cast().map(Self::MathFrac),
            SyntaxKind::Ident => node.cast().map(Self::Ident),
            SyntaxKind::None => node.cast().map(Self::None),
            SyntaxKind::Auto => node.cast().map(Self::Auto),
            SyntaxKind::Bool => node.cast().map(Self::Bool),
            SyntaxKind::Int => node.cast().map(Self::Int),
            SyntaxKind::Float => node.cast().map(Self::Float),
            SyntaxKind::Numeric => node.cast().map(Self::Numeric),
            SyntaxKind::Str => node.cast().map(Self::Str),
            SyntaxKind::CodeBlock => node.cast().map(Self::Code),
            SyntaxKind::ContentBlock => node.cast().map(Self::Content),
            SyntaxKind::Parenthesized => node.cast().map(Self::Parenthesized),
            SyntaxKind::Array => node.cast().map(Self::Array),
            SyntaxKind::Dict => node.cast().map(Self::Dict),
            SyntaxKind::Unary => node.cast().map(Self::Unary),
            SyntaxKind::Binary => node.cast().map(Self::Binary),
            SyntaxKind::FieldAccess => node.cast().map(Self::FieldAccess),
            SyntaxKind::FuncCall => node.cast().map(Self::FuncCall),
            SyntaxKind::Closure => node.cast().map(Self::Closure),
            SyntaxKind::LetBinding => node.cast().map(Self::Let),
            SyntaxKind::SetRule => node.cast().map(Self::Set),
            SyntaxKind::ShowRule => node.cast().map(Self::Show),
            SyntaxKind::Conditional => node.cast().map(Self::Conditional),
            SyntaxKind::WhileLoop => node.cast().map(Self::While),
            SyntaxKind::ForLoop => node.cast().map(Self::For),
            SyntaxKind::ModuleImport => node.cast().map(Self::Import),
            SyntaxKind::ModuleInclude => node.cast().map(Self::Include),
            SyntaxKind::LoopBreak => node.cast().map(Self::Break),
            SyntaxKind::LoopContinue => node.cast().map(Self::Continue),
            SyntaxKind::FuncReturn => node.cast().map(Self::Return),
            _ => Option::None,
        }
    }

    fn as_untyped(&self) -> &SyntaxNode {
        match self {
            Self::Text(v) => v.as_untyped(),
            Self::Space(v) => v.as_untyped(),
            Self::Linebreak(v) => v.as_untyped(),
            Self::Parbreak(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::Term(v) => v.as_untyped(),
            Self::Formula(v) => v.as_untyped(),
            Self::Math(v) => v.as_untyped(),
            Self::MathIdent(v) => v.as_untyped(),
            Self::MathAlignPoint(v) => v.as_untyped(),
            Self::MathDelimited(v) => v.as_untyped(),
            Self::MathAttach(v) => v.as_untyped(),
            Self::MathFrac(v) => v.as_untyped(),
            Self::Ident(v) => v.as_untyped(),
            Self::None(v) => v.as_untyped(),
            Self::Auto(v) => v.as_untyped(),
            Self::Bool(v) => v.as_untyped(),
            Self::Int(v) => v.as_untyped(),
            Self::Float(v) => v.as_untyped(),
            Self::Numeric(v) => v.as_untyped(),
            Self::Str(v) => v.as_untyped(),
            Self::Code(v) => v.as_untyped(),
            Self::Content(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::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 {
    /// Can this expression be embedded into markup with a hashtag?
    pub fn hashtag(&self) -> bool {
        match self {
            Self::Ident(_) => true,
            Self::None(_) => true,
            Self::Auto(_) => true,
            Self::Bool(_) => true,
            Self::Int(_) => true,
            Self::Float(_) => true,
            Self::Numeric(_) => true,
            Self::Str(_) => true,
            Self::Code(_) => true,
            Self::Content(_) => true,
            Self::Array(_) => true,
            Self::Dict(_) => true,
            Self::Parenthesized(_) => true,
            Self::FieldAccess(_) => true,
            Self::FuncCall(_) => true,
            Self::Let(_) => true,
            Self::Set(_) => true,
            Self::Show(_) => true,
            Self::Conditional(_) => true,
            Self::While(_) => true,
            Self::For(_) => true,
            Self::Import(_) => true,
            Self::Include(_) => true,
            Self::Break(_) => true,
            Self::Continue(_) => true,
            Self::Return(_) => true,
            _ => false,
        }
    }

    /// Is this a literal?
    pub fn is_literal(&self) -> bool {
        match self {
            Self::None(_) => true,
            Self::Auto(_) => true,
            Self::Bool(_) => true,
            Self::Int(_) => true,
            Self::Float(_) => true,
            Self::Numeric(_) => true,
            Self::Str(_) => true,
            _ => false,
        }
    }
}

impl Default for Expr {
    fn default() -> Self {
        Expr::Space(Space::default())
    }
}

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

impl Text {
    /// Get the text.
    pub fn get(&self) -> &EcoString {
        self.0.text()
    }
}

node! {
    /// Whitespace in markup or math. Has at most one newline in markup, as more
    /// indicate a paragraph break.
    Space
}

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

node! {
    /// A paragraph break, indicated by one or multiple blank lines.
    Parbreak
}

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

impl Escape {
    /// Get the escaped character.
    pub fn get(&self) -> char {
        let mut s = Scanner::new(self.0.text());
        s.expect('\\');
        if s.eat_if("u{") {
            let hex = s.eat_while(char::is_ascii_hexdigit);
            u32::from_str_radix(hex, 16)
                .ok()
                .and_then(std::char::from_u32)
                .unwrap_or_default()
        } else {
            s.eat().unwrap_or_default()
        }
    }
}

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

impl Shorthand {
    /// A list of all shorthands.
    pub const LIST: &[(&'static str, char)] = &[
        ("~", '\u{00A0}'),
        ("--", '\u{2013}'),
        ("---", '\u{2014}'),
        ("-?", '\u{00AD}'),
        ("...", '…'),
        ("*", '∗'),
        ("!=", '≠'),
        ("<<", '≪'),
        ("<<<", '⋘'),
        (">>", '≫'),
        (">>>", '⋙'),
        ("<=", '≤'),
        (">=", '≥'),
        ("<-", '←'),
        ("->", '→'),
        ("=>", '⇒'),
        ("|->", '↦'),
        ("|=>", '⤇'),
        ("<->", '↔'),
        ("<=>", '⇔'),
        (":=", '≔'),
        ("[|", '⟦'),
        ("|]", '⟧'),
        ("||", '‖'),
    ];

    /// Get the shorthanded character.
    pub fn get(&self) -> char {
        let text = self.0.text().as_str();
        Self::LIST
            .iter()
            .find(|&&(s, _)| s == text)
            .map_or_else(char::default, |&(_, c)| c)
    }
}

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

impl SmartQuote {
    /// Whether this is a double quote.
    pub fn double(&self) -> bool {
        self.0.text() == "\""
    }
}

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

impl Strong {
    /// The contents of the strong node.
    pub fn body(&self) -> Markup {
        self.0.cast_first_match().unwrap_or_default()
    }
}

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

impl Emph {
    /// The contents of the emphasis node.
    pub fn body(&self) -> Markup {
        self.0.cast_first_match().unwrap_or_default()
    }
}

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

impl Raw {
    /// The trimmed raw text.
    pub fn text(&self) -> EcoString {
        let mut text = self.0.text().as_str();
        let blocky = text.starts_with("```");
        text = text.trim_matches('`');

        // Trim tag, one space at the start, and one space at the end if the
        // last non-whitespace char is a backtick.
        if blocky {
            let mut s = Scanner::new(text);
            if s.eat_if(is_id_start) {
                s.eat_while(is_id_continue);
            }
            text = s.after();
            text = text.strip_prefix(' ').unwrap_or(text);
            if text.trim_end().ends_with('`') {
                text = text.strip_suffix(' ').unwrap_or(text);
            }
        }

        // Split into lines.
        let mut lines = split_newlines(text);

        if blocky {
            let dedent = lines
                .iter()
                .skip(1)
                .map(|line| line.chars().take_while(|c| c.is_whitespace()).count())
                .min()
                .unwrap_or(0);

            // Dedent based on column, but not for the first line.
            for line in lines.iter_mut().skip(1) {
                let offset = line.chars().take(dedent).map(char::len_utf8).sum();
                *line = &line[offset..];
            }

            let is_whitespace = |line: &&str| line.chars().all(char::is_whitespace);

            // Trims a sequence of whitespace followed by a newline at the start.
            if lines.first().map_or(false, is_whitespace) {
                lines.remove(0);
            }

            // Trims a newline followed by a sequence of whitespace at the end.
            if lines.last().map_or(false, is_whitespace) {
                lines.pop();
            }
        }

        lines.join("\n").into()
    }

    /// An optional identifier specifying the language to syntax-highlight in.
    pub fn lang(&self) -> Option<&str> {
        let inner = self.0.text().trim_start_matches('`');
        let mut s = Scanner::new(inner);
        s.eat_if(is_id_start).then(|| {
            s.eat_while(is_id_continue);
            s.before()
        })
    }

    /// Whether the raw text should be displayed in a separate block.
    pub fn block(&self) -> bool {
        let text = self.0.text();
        text.starts_with("```") && text.chars().any(is_newline)
    }
}

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

impl Link {
    /// Get the URL.
    pub fn get(&self) -> &EcoString {
        self.0.text()
    }
}

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

impl Label {
    /// Get the label's text.
    pub fn get(&self) -> &str {
        self.0.text().trim_start_matches('<').trim_end_matches('>')
    }
}

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

impl Ref {
    /// Get the target.
    pub fn get(&self) -> &str {
        self.0.text().trim_start_matches('@')
    }
}

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

impl Heading {
    /// The contents of the heading.
    pub fn body(&self) -> Markup {
        self.0.cast_first_match().unwrap_or_default()
    }

    /// The section depth (numer of equals signs).
    pub fn level(&self) -> NonZeroUsize {
        self.0
            .children()
            .find(|node| node.kind() == SyntaxKind::HeadingMarker)
            .and_then(|node| node.len().try_into().ok())
            .unwrap_or(NonZeroUsize::new(1).unwrap())
    }
}

node! {
    /// An item in a bullet list: `- ...`.
    ListItem
}

impl ListItem {
    /// The contents of the list item.
    pub fn body(&self) -> Markup {
        self.0.cast_first_match().unwrap_or_default()
    }
}

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

impl EnumItem {
    /// The explicit numbering, if any: `23.`.
    pub fn number(&self) -> Option<NonZeroUsize> {
        self.0.children().find_map(|node| match node.kind() {
            SyntaxKind::EnumMarker => node.text().trim_end_matches('.').parse().ok(),
            _ => Option::None,
        })
    }

    /// The contents of the list item.
    pub fn body(&self) -> Markup {
        self.0.cast_first_match().unwrap_or_default()
    }
}

node! {
    /// An item in a term list: `/ Term: Details`.
    TermItem
}

impl TermItem {
    /// The term described by the item.
    pub fn term(&self) -> Markup {
        self.0.cast_first_match().unwrap_or_default()
    }

    /// The description of the term.
    pub fn description(&self) -> Markup {
        self.0.cast_last_match().unwrap_or_default()
    }
}

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

impl Formula {
    /// The contained math.
    pub fn body(&self) -> Math {
        self.0.cast_first_match().unwrap_or_default()
    }

    /// Whether the formula should be displayed as a separate block.
    pub fn block(&self) -> bool {
        let is_space = |node: Option<&SyntaxNode>| {
            node.map(SyntaxNode::kind) == Some(SyntaxKind::Space)
        };
        is_space(self.0.children().nth(1)) && is_space(self.0.children().nth_back(1))
    }
}

node! {
    /// Math markup.
    Math
}

impl Math {
    /// The expressions the mathematical content consists of.
    pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
        self.0.children().filter_map(Expr::cast_with_space)
    }
}

node! {
    /// An identifier in a math formula: `pi`.
    MathIdent
}

impl MathIdent {
    /// Get the identifier.
    pub fn get(&self) -> &EcoString {
        self.0.text()
    }

    /// Take out the contained identifier.
    pub fn take(self) -> EcoString {
        self.0.into_text()
    }

    /// Get the identifier as a string slice.
    pub fn as_str(&self) -> &str {
        self.get()
    }
}

impl Deref for MathIdent {
    type Target = str;

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

node! {
    /// An alignment point in a formula: `&`.
    MathAlignPoint
}

node! {
    /// Matched delimiters surrounding math in a formula: `[x + y]`.
    MathDelimited
}

impl MathDelimited {
    /// The opening delimiter.
    pub fn open(&self) -> Expr {
        self.0.cast_first_match().unwrap_or_default()
    }

    /// The contents, including the delimiters.
    pub fn body(&self) -> Math {
        self.0.cast_first_match().unwrap_or_default()
    }

    /// The closing delimiter.
    pub fn close(&self) -> Expr {
        self.0.cast_last_match().unwrap_or_default()
    }
}

node! {
    /// A base with optional attachments in a formula: `a_1^2`.
    MathAttach
}

impl MathAttach {
    /// The base, to which things are attached.
    pub fn base(&self) -> Expr {
        self.0.cast_first_match().unwrap_or_default()
    }

    /// The bottom attachment.
    pub fn bottom(&self) -> Option<Expr> {
        self.0
            .children()
            .skip_while(|node| !matches!(node.kind(), SyntaxKind::Underscore))
            .find_map(SyntaxNode::cast)
    }

    /// The top attachment.
    pub fn top(&self) -> Option<Expr> {
        self.0
            .children()
            .skip_while(|node| !matches!(node.kind(), SyntaxKind::Hat))
            .find_map(SyntaxNode::cast)
    }
}

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

impl MathFrac {
    /// The numerator.
    pub fn num(&self) -> Expr {
        self.0.cast_first_match().unwrap_or_default()
    }

    /// The denominator.
    pub fn denom(&self) -> Expr {
        self.0.cast_last_match().unwrap_or_default()
    }
}

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

impl Ident {
    /// Get the identifier.
    pub fn get(&self) -> &EcoString {
        self.0.text()
    }

    /// Take out the contained identifier.
    pub fn take(self) -> EcoString {
        self.0.into_text()
    }

    /// Get the identifier as a string slice.
    pub fn as_str(&self) -> &str {
        self.get()
    }
}

impl Deref for Ident {
    type Target = str;

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

node! {
    /// The `none` literal.
    None
}

node! {
    /// The `auto` literal.
    Auto
}

node! {
    /// A boolean: `true`, `false`.
    Bool
}

impl Bool {
    /// Get the boolean value.
    pub fn get(&self) -> bool {
        self.0.text() == "true"
    }
}

node! {
    /// An integer: `120`.
    Int
}

impl Int {
    /// Get the integer value.
    pub fn get(&self) -> i64 {
        self.0.text().parse().unwrap_or_default()
    }
}

node! {
    /// A floating-point number: `1.2`, `10e-4`.
    Float
}

impl Float {
    /// Get the floating-point value.
    pub fn get(&self) -> f64 {
        self.0.text().parse().unwrap_or_default()
    }
}

node! {
    /// A numeric value with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`.
    Numeric
}

impl Numeric {
    /// Get the numeric value and unit.
    pub fn get(&self) -> (f64, Unit) {
        let text = self.0.text();
        let count = text
            .chars()
            .rev()
            .take_while(|c| matches!(c, 'a'..='z' | '%'))
            .count();

        let split = text.len() - count;
        let value = text[..split].parse().unwrap_or_default();
        let unit = match &text[split..] {
            "pt" => Unit::Length(AbsUnit::Pt),
            "mm" => Unit::Length(AbsUnit::Mm),
            "cm" => Unit::Length(AbsUnit::Cm),
            "in" => Unit::Length(AbsUnit::In),
            "deg" => Unit::Angle(AngleUnit::Deg),
            "rad" => Unit::Angle(AngleUnit::Rad),
            "em" => Unit::Em,
            "fr" => Unit::Fr,
            "%" => Unit::Percent,
            _ => Unit::Percent,
        };

        (value, unit)
    }
}

/// Unit of a numeric value.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Unit {
    /// An absolute length unit.
    Length(AbsUnit),
    /// An angular unit.
    Angle(AngleUnit),
    /// Font-relative: `1em` is the same as the font size.
    Em,
    /// Fractions: `fr`.
    Fr,
    /// Percentage: `%`.
    Percent,
}

node! {
    /// A quoted string: `"..."`.
    Str
}

impl Str {
    /// Get the string value with resolved escape sequences.
    pub fn get(&self) -> EcoString {
        let text = self.0.text();
        let unquoted = &text[1..text.len() - 1];
        if !unquoted.contains('\\') {
            return unquoted.into();
        }

        let mut out = EcoString::with_capacity(unquoted.len());
        let mut s = Scanner::new(unquoted);

        while let Some(c) = s.eat() {
            if c != '\\' {
                out.push(c);
                continue;
            }

            let start = s.locate(-1);
            match s.eat() {
                Some('\\') => out.push('\\'),
                Some('"') => out.push('"'),
                Some('n') => out.push('\n'),
                Some('r') => out.push('\r'),
                Some('t') => out.push('\t'),
                Some('u') if s.eat_if('{') => {
                    let sequence = s.eat_while(char::is_ascii_hexdigit);
                    s.eat_if('}');

                    match u32::from_str_radix(sequence, 16)
                        .ok()
                        .and_then(std::char::from_u32)
                    {
                        Some(c) => out.push(c),
                        Option::None => out.push_str(s.from(start)),
                    }
                }
                _ => out.push_str(s.from(start)),
            }
        }

        out
    }
}

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

impl CodeBlock {
    /// The contained code.
    pub fn body(&self) -> Code {
        self.0.cast_first_match().unwrap_or_default()
    }
}

node! {
    /// Code.
    Code
}

impl Code {
    /// The list of expressions contained in the code.
    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_match().unwrap_or_default()
    }
}

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

impl Parenthesized {
    /// The wrapped expression.
    pub fn expr(&self) -> Expr {
        self.0.cast_first_match().unwrap_or_default()
    }
}

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 AstNode for ArrayItem {
    fn from_untyped(node: &SyntaxNode) -> Option<Self> {
        match node.kind() {
            SyntaxKind::Spread => node.cast_first_match().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 AstNode for DictItem {
    fn from_untyped(node: &SyntaxNode) -> Option<Self> {
        match node.kind() {
            SyntaxKind::Named => node.cast().map(Self::Named),
            SyntaxKind::Keyed => node.cast().map(Self::Keyed),
            SyntaxKind::Spread => node.cast_first_match().map(Self::Spread),
            _ => Option::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_match().unwrap_or_default()
    }

    /// The right-hand side of the pair: `3pt`.
    pub fn expr(&self) -> Expr {
        self.0.cast_last_match().unwrap_or_default()
    }
}

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

impl Keyed {
    /// The key: `"spacy key"`.
    pub fn key(&self) -> Str {
        self.0
            .children()
            .find_map(|node| node.cast::<Str>())
            .unwrap_or_default()
    }

    /// The right-hand side of the pair: `true`.
    pub fn expr(&self) -> Expr {
        self.0.cast_last_match().unwrap_or_default()
    }
}

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

impl Unary {
    /// The operator: `-`.
    pub fn op(&self) -> UnOp {
        self.0
            .children()
            .find_map(|node| UnOp::from_kind(node.kind()))
            .unwrap_or(UnOp::Pos)
    }

    /// The expression to operate on: `x`.
    pub fn expr(&self) -> Expr {
        self.0.cast_last_match().unwrap_or_default()
    }
}

/// 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_kind(token: SyntaxKind) -> Option<Self> {
        Some(match token {
            SyntaxKind::Plus => Self::Pos,
            SyntaxKind::Minus => Self::Neg,
            SyntaxKind::Not => Self::Not,
            _ => return Option::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() {
                SyntaxKind::Not => {
                    not = true;
                    Option::None
                }
                SyntaxKind::In if not => Some(BinOp::NotIn),
                _ => BinOp::from_kind(node.kind()),
            })
            .unwrap_or(BinOp::Add)
    }

    /// The left-hand side of the operation: `a`.
    pub fn lhs(&self) -> Expr {
        self.0.cast_first_match().unwrap_or_default()
    }

    /// The right-hand side of the operation: `b`.
    pub fn rhs(&self) -> Expr {
        self.0.cast_last_match().unwrap_or_default()
    }
}

/// 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_kind(token: SyntaxKind) -> Option<Self> {
        Some(match token {
            SyntaxKind::Plus => Self::Add,
            SyntaxKind::Minus => Self::Sub,
            SyntaxKind::Star => Self::Mul,
            SyntaxKind::Slash => Self::Div,
            SyntaxKind::And => Self::And,
            SyntaxKind::Or => Self::Or,
            SyntaxKind::EqEq => Self::Eq,
            SyntaxKind::ExclEq => Self::Neq,
            SyntaxKind::Lt => Self::Lt,
            SyntaxKind::LtEq => Self::Leq,
            SyntaxKind::Gt => Self::Gt,
            SyntaxKind::GtEq => Self::Geq,
            SyntaxKind::Eq => Self::Assign,
            SyntaxKind::In => Self::In,
            SyntaxKind::PlusEq => Self::AddAssign,
            SyntaxKind::HyphEq => Self::SubAssign,
            SyntaxKind::StarEq => Self::MulAssign,
            SyntaxKind::SlashEq => Self::DivAssign,
            _ => return Option::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_match().unwrap_or_default()
    }

    /// The name of the field.
    pub fn field(&self) -> Ident {
        self.0.cast_last_match().unwrap_or_default()
    }
}

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

impl FuncCall {
    /// The function to call.
    pub fn callee(&self) -> Expr {
        self.0.cast_first_match().unwrap_or_default()
    }

    /// The arguments to the function.
    pub fn args(&self) -> Args {
        self.0.cast_last_match().unwrap_or_default()
    }
}

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 AstNode for Arg {
    fn from_untyped(node: &SyntaxNode) -> Option<Self> {
        match node.kind() {
            SyntaxKind::Named => node.cast().map(Self::Named),
            SyntaxKind::Spread => node.cast_first_match().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.children().next()?.cast()
    }

    /// The parameter bindings.
    pub fn params(&self) -> impl DoubleEndedIterator<Item = Param> + '_ {
        self.0
            .children()
            .find(|x| x.kind() == SyntaxKind::Params)
            .map_or([].iter(), |params| params.children())
            .filter_map(SyntaxNode::cast)
    }

    /// The body of the closure.
    pub fn body(&self) -> Expr {
        self.0.cast_last_match().unwrap_or_default()
    }
}

/// 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 AstNode for Param {
    fn from_untyped(node: &SyntaxNode) -> Option<Self> {
        match node.kind() {
            SyntaxKind::Ident => node.cast().map(Self::Pos),
            SyntaxKind::Named => node.cast().map(Self::Named),
            SyntaxKind::Spread => node.cast_first_match().map(Self::Sink),
            _ => Option::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_match() {
            Some(Expr::Ident(binding)) => binding,
            Some(Expr::Closure(closure)) => closure.name().unwrap_or_default(),
            _ => Ident::default(),
        }
    }

    /// The expression the binding is initialized with.
    pub fn init(&self) -> Option<Expr> {
        if self.0.cast_first_match::<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_match()
        }
    }
}

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

impl SetRule {
    /// The function to set style properties for.
    pub fn target(&self) -> Expr {
        self.0.cast_first_match().unwrap_or_default()
    }

    /// The style properties to set.
    pub fn args(&self) -> Args {
        self.0.cast_last_match().unwrap_or_default()
    }

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

    /// The transformation recipe.
    pub fn transform(&self) -> Expr {
        self.0.cast_last_match().unwrap_or_default()
    }
}

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_match().unwrap_or_default()
    }

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

    /// 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_match().unwrap_or_default()
    }

    /// The expression to evaluate while the condition is true.
    pub fn body(&self) -> Expr {
        self.0.cast_last_match().unwrap_or_default()
    }
}

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_match().unwrap_or_default()
    }

    /// The expression to iterate over.
    pub fn iter(&self) -> Expr {
        self.0.cast_first_match().unwrap_or_default()
    }

    /// The expression to evaluate for each iteration.
    pub fn body(&self) -> Expr {
        self.0.cast_last_match().unwrap_or_default()
    }
}

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 {
            Option::None
        }
    }

    /// The value part of the pattern.
    pub fn value(&self) -> Ident {
        self.0.cast_last_match().unwrap_or_default()
    }
}

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

impl ModuleImport {
    /// The module or path from which the items should be imported.
    pub fn source(&self) -> Expr {
        self.0.cast_first_match().unwrap_or_default()
    }

    /// The items to be imported.
    pub fn imports(&self) -> Option<Imports> {
        self.0.children().find_map(|node| match node.kind() {
            SyntaxKind::Star => Some(Imports::Wildcard),
            SyntaxKind::ImportItems => {
                let items = node.children().filter_map(SyntaxNode::cast).collect();
                Some(Imports::Items(items))
            }
            _ => Option::None,
        })
    }
}

/// 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 module or path from which the content should be included.
    pub fn source(&self) -> Expr {
        self.0.cast_last_match().unwrap_or_default()
    }
}

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_match()
    }
}
