//! Token types for the ECMAScript lexer.
//!
//! A [`Token`] is a [`Spanned<TokenKind>`].  Spans are sourced from
//! [`ecma_syntax_cat::span::Span`] so downstream layers can compose
//! positions across the lexer / parser boundary without conversion.
//!
//! [`Spanned<TokenKind>`]: ecma_syntax_cat::span::Spanned

use ecma_syntax_cat::span::Spanned;

/// A token paired with its source span.
pub type Token = Spanned<TokenKind>;

/// The kind of an ECMAScript token.
#[derive(Debug, Clone, PartialEq)]
pub enum TokenKind {
    // ----- Reserved keywords -------------------------------------------
    /// `await`
    KwAwait,
    /// `break`
    KwBreak,
    /// `case`
    KwCase,
    /// `catch`
    KwCatch,
    /// `class`
    KwClass,
    /// `const`
    KwConst,
    /// `continue`
    KwContinue,
    /// `debugger`
    KwDebugger,
    /// `default`
    KwDefault,
    /// `delete`
    KwDelete,
    /// `do`
    KwDo,
    /// `else`
    KwElse,
    /// `enum` (reserved, currently unused)
    KwEnum,
    /// `export`
    KwExport,
    /// `extends`
    KwExtends,
    /// `false`
    KwFalse,
    /// `finally`
    KwFinally,
    /// `for`
    KwFor,
    /// `function`
    KwFunction,
    /// `if`
    KwIf,
    /// `implements` (strict reserved)
    KwImplements,
    /// `import`
    KwImport,
    /// `in`
    KwIn,
    /// `instanceof`
    KwInstanceof,
    /// `interface` (strict reserved)
    KwInterface,
    /// `let` (strict reserved; contextual in sloppy mode)
    KwLet,
    /// `new`
    KwNew,
    /// `null`
    KwNull,
    /// `package` (strict reserved)
    KwPackage,
    /// `private` (strict reserved)
    KwPrivate,
    /// `protected` (strict reserved)
    KwProtected,
    /// `public` (strict reserved)
    KwPublic,
    /// `return`
    KwReturn,
    /// `static` (strict reserved; contextual in class bodies)
    KwStatic,
    /// `super`
    KwSuper,
    /// `switch`
    KwSwitch,
    /// `this`
    KwThis,
    /// `throw`
    KwThrow,
    /// `true`
    KwTrue,
    /// `try`
    KwTry,
    /// `typeof`
    KwTypeof,
    /// `var`
    KwVar,
    /// `void`
    KwVoid,
    /// `while`
    KwWhile,
    /// `with`
    KwWith,
    /// `yield`
    KwYield,

    // ----- Identifiers --------------------------------------------------
    /// A non-keyword identifier (including contextual identifiers like
    /// `of`, `as`, `from`, `async`, `get`, `set`, `target`, `meta`).
    Identifier(String),
    /// A `#name` private class-field identifier.  The leading `#` is not
    /// included in the stored string.
    PrivateIdentifier(String),

    // ----- Literals -----------------------------------------------------
    /// A numeric literal (already converted to `f64`).
    Number(f64),
    /// A `BigInt` literal storing the decimal text (without trailing `n`).
    BigInt(String),
    /// A string literal (after escape processing).
    String(String),
    /// A regular expression literal.
    RegExp {
        /// Pattern text between the slashes.
        pattern: String,
        /// Flag characters after the closing slash.
        flags: String,
    },
    /// A template literal with no interpolation (`` `text` ``).
    TemplateNoSubstitution(String),
    /// The leading slice of a template literal: `` `text${ ``.
    TemplateHead(String),
    /// A middle slice of a template literal: `}text${`.
    TemplateMiddle(String),
    /// The trailing slice of a template literal: `` }text` ``.
    TemplateTail(String),

    // ----- Punctuators --------------------------------------------------
    /// `(`
    LParen,
    /// `)`
    RParen,
    /// `[`
    LBracket,
    /// `]`
    RBracket,
    /// `{`
    LBrace,
    /// `}`
    RBrace,
    /// `,`
    Comma,
    /// `;`
    Semicolon,
    /// `:`
    Colon,
    /// `.`
    Dot,
    /// `?.`
    OptionalChain,
    /// `...`
    Spread,
    /// `=>`
    Arrow,
    /// `?`
    Question,

    // ----- Comparison operators ----------------------------------------
    /// `==`
    EqEq,
    /// `===`
    EqEqEq,
    /// `!=`
    BangEq,
    /// `!==`
    BangEqEq,
    /// `<`
    Lt,
    /// `<=`
    LtEq,
    /// `>`
    Gt,
    /// `>=`
    GtEq,

    // ----- Arithmetic operators ----------------------------------------
    /// `+`
    Plus,
    /// `-`
    Minus,
    /// `*`
    Star,
    /// `/`
    Slash,
    /// `%`
    Percent,
    /// `**`
    StarStar,

    // ----- Update operators --------------------------------------------
    /// `++`
    PlusPlus,
    /// `--`
    MinusMinus,

    // ----- Bitwise operators -------------------------------------------
    /// `&`
    Amp,
    /// `|`
    Pipe,
    /// `^`
    Caret,
    /// `~`
    Tilde,
    /// `<<`
    LtLt,
    /// `>>`
    GtGt,
    /// `>>>`
    GtGtGt,

    // ----- Logical operators -------------------------------------------
    /// `&&`
    AmpAmp,
    /// `||`
    PipePipe,
    /// `??`
    QQ,
    /// `!`
    Bang,

    // ----- Assignment operators ----------------------------------------
    /// `=`
    Eq,
    /// `+=`
    PlusEq,
    /// `-=`
    MinusEq,
    /// `*=`
    StarEq,
    /// `/=`
    SlashEq,
    /// `%=`
    PercentEq,
    /// `**=`
    StarStarEq,
    /// `<<=`
    LtLtEq,
    /// `>>=`
    GtGtEq,
    /// `>>>=`
    GtGtGtEq,
    /// `&=`
    AmpEq,
    /// `|=`
    PipeEq,
    /// `^=`
    CaretEq,
    /// `&&=`
    AmpAmpEq,
    /// `||=`
    PipePipeEq,
    /// `??=`
    QQEq,

    // ----- End of file --------------------------------------------------
    /// Marker emitted once at the end of the input.
    Eof,
}

/// Eq impl matches IEEE-754 equality for `Number` (so `NaN != NaN`),
/// consistent with ECMA-262 strict equality.
impl Eq for TokenKind {}

impl TokenKind {
    /// Whether a `/` immediately following a token of this kind should be
    /// lexed as the start of a regex literal.
    ///
    /// Returns `false` after tokens that conclude an expression
    /// (identifiers, literals, `)`, `]`, `}`, postfix `++`/`--`, and the
    /// keyword literals `this`/`super`/`true`/`false`/`null`), where `/`
    /// must be division.  Returns `true` everywhere else, where `/` starts
    /// a regex literal.
    #[must_use]
    pub fn allows_regex_after(&self) -> bool {
        !ends_expression(self)
    }
}

#[allow(clippy::too_many_lines)] // exhaustive enumeration over the ~110-variant TokenKind
fn ends_expression(kind: &TokenKind) -> bool {
    match kind {
        // Identifiers and literals: complete expression values.
        TokenKind::Identifier(_)
        | TokenKind::PrivateIdentifier(_)
        | TokenKind::Number(_)
        | TokenKind::BigInt(_)
        | TokenKind::String(_)
        | TokenKind::RegExp { .. }
        | TokenKind::TemplateNoSubstitution(_)
        | TokenKind::TemplateTail(_)
        // Closing brackets close a sub-expression.
        | TokenKind::RParen
        | TokenKind::RBracket
        | TokenKind::RBrace
        // Postfix update operators bind to the preceding identifier value.
        | TokenKind::PlusPlus
        | TokenKind::MinusMinus
        // Keyword literals.
        | TokenKind::KwThis
        | TokenKind::KwSuper
        | TokenKind::KwTrue
        | TokenKind::KwFalse
        | TokenKind::KwNull => true,

        // Everything else: `/` starts a regex.
        TokenKind::KwAwait
        | TokenKind::KwBreak
        | TokenKind::KwCase
        | TokenKind::KwCatch
        | TokenKind::KwClass
        | TokenKind::KwConst
        | TokenKind::KwContinue
        | TokenKind::KwDebugger
        | TokenKind::KwDefault
        | TokenKind::KwDelete
        | TokenKind::KwDo
        | TokenKind::KwElse
        | TokenKind::KwEnum
        | TokenKind::KwExport
        | TokenKind::KwExtends
        | TokenKind::KwFinally
        | TokenKind::KwFor
        | TokenKind::KwFunction
        | TokenKind::KwIf
        | TokenKind::KwImplements
        | TokenKind::KwImport
        | TokenKind::KwIn
        | TokenKind::KwInstanceof
        | TokenKind::KwInterface
        | TokenKind::KwLet
        | TokenKind::KwNew
        | TokenKind::KwPackage
        | TokenKind::KwPrivate
        | TokenKind::KwProtected
        | TokenKind::KwPublic
        | TokenKind::KwReturn
        | TokenKind::KwStatic
        | TokenKind::KwSwitch
        | TokenKind::KwThrow
        | TokenKind::KwTry
        | TokenKind::KwTypeof
        | TokenKind::KwVar
        | TokenKind::KwVoid
        | TokenKind::KwWhile
        | TokenKind::KwWith
        | TokenKind::KwYield
        | TokenKind::TemplateHead(_)
        | TokenKind::TemplateMiddle(_)
        | TokenKind::LParen
        | TokenKind::LBracket
        | TokenKind::LBrace
        | TokenKind::Comma
        | TokenKind::Semicolon
        | TokenKind::Colon
        | TokenKind::Dot
        | TokenKind::OptionalChain
        | TokenKind::Spread
        | TokenKind::Arrow
        | TokenKind::Question
        | TokenKind::EqEq
        | TokenKind::EqEqEq
        | TokenKind::BangEq
        | TokenKind::BangEqEq
        | TokenKind::Lt
        | TokenKind::LtEq
        | TokenKind::Gt
        | TokenKind::GtEq
        | TokenKind::Plus
        | TokenKind::Minus
        | TokenKind::Star
        | TokenKind::Slash
        | TokenKind::Percent
        | TokenKind::StarStar
        | TokenKind::Amp
        | TokenKind::Pipe
        | TokenKind::Caret
        | TokenKind::Tilde
        | TokenKind::LtLt
        | TokenKind::GtGt
        | TokenKind::GtGtGt
        | TokenKind::AmpAmp
        | TokenKind::PipePipe
        | TokenKind::QQ
        | TokenKind::Bang
        | TokenKind::Eq
        | TokenKind::PlusEq
        | TokenKind::MinusEq
        | TokenKind::StarEq
        | TokenKind::SlashEq
        | TokenKind::PercentEq
        | TokenKind::StarStarEq
        | TokenKind::LtLtEq
        | TokenKind::GtGtEq
        | TokenKind::GtGtGtEq
        | TokenKind::AmpEq
        | TokenKind::PipeEq
        | TokenKind::CaretEq
        | TokenKind::AmpAmpEq
        | TokenKind::PipePipeEq
        | TokenKind::QQEq
        | TokenKind::Eof => false,
    }
}

impl std::fmt::Display for TokenKind {
    #[allow(clippy::too_many_lines)] // one arm per TokenKind variant
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::KwAwait => f.write_str("await"),
            Self::KwBreak => f.write_str("break"),
            Self::KwCase => f.write_str("case"),
            Self::KwCatch => f.write_str("catch"),
            Self::KwClass => f.write_str("class"),
            Self::KwConst => f.write_str("const"),
            Self::KwContinue => f.write_str("continue"),
            Self::KwDebugger => f.write_str("debugger"),
            Self::KwDefault => f.write_str("default"),
            Self::KwDelete => f.write_str("delete"),
            Self::KwDo => f.write_str("do"),
            Self::KwElse => f.write_str("else"),
            Self::KwEnum => f.write_str("enum"),
            Self::KwExport => f.write_str("export"),
            Self::KwExtends => f.write_str("extends"),
            Self::KwFalse => f.write_str("false"),
            Self::KwFinally => f.write_str("finally"),
            Self::KwFor => f.write_str("for"),
            Self::KwFunction => f.write_str("function"),
            Self::KwIf => f.write_str("if"),
            Self::KwImplements => f.write_str("implements"),
            Self::KwImport => f.write_str("import"),
            Self::KwIn => f.write_str("in"),
            Self::KwInstanceof => f.write_str("instanceof"),
            Self::KwInterface => f.write_str("interface"),
            Self::KwLet => f.write_str("let"),
            Self::KwNew => f.write_str("new"),
            Self::KwNull => f.write_str("null"),
            Self::KwPackage => f.write_str("package"),
            Self::KwPrivate => f.write_str("private"),
            Self::KwProtected => f.write_str("protected"),
            Self::KwPublic => f.write_str("public"),
            Self::KwReturn => f.write_str("return"),
            Self::KwStatic => f.write_str("static"),
            Self::KwSuper => f.write_str("super"),
            Self::KwSwitch => f.write_str("switch"),
            Self::KwThis => f.write_str("this"),
            Self::KwThrow => f.write_str("throw"),
            Self::KwTrue => f.write_str("true"),
            Self::KwTry => f.write_str("try"),
            Self::KwTypeof => f.write_str("typeof"),
            Self::KwVar => f.write_str("var"),
            Self::KwVoid => f.write_str("void"),
            Self::KwWhile => f.write_str("while"),
            Self::KwWith => f.write_str("with"),
            Self::KwYield => f.write_str("yield"),
            Self::Identifier(name) => f.write_str(name),
            Self::PrivateIdentifier(name) => write!(f, "#{name}"),
            Self::Number(n) => write!(f, "{n}"),
            Self::BigInt(digits) => write!(f, "{digits}n"),
            Self::String(content) => write!(f, "{content:?}"),
            Self::RegExp { pattern, flags } => write!(f, "/{pattern}/{flags}"),
            Self::TemplateNoSubstitution(content) => write!(f, "`{content}`"),
            Self::TemplateHead(content) => write!(f, "`{content}${{"),
            Self::TemplateMiddle(content) => write!(f, "}}{content}${{"),
            Self::TemplateTail(content) => write!(f, "}}{content}`"),
            Self::LParen => f.write_str("("),
            Self::RParen => f.write_str(")"),
            Self::LBracket => f.write_str("["),
            Self::RBracket => f.write_str("]"),
            Self::LBrace => f.write_str("{"),
            Self::RBrace => f.write_str("}"),
            Self::Comma => f.write_str(","),
            Self::Semicolon => f.write_str(";"),
            Self::Colon => f.write_str(":"),
            Self::Dot => f.write_str("."),
            Self::OptionalChain => f.write_str("?."),
            Self::Spread => f.write_str("..."),
            Self::Arrow => f.write_str("=>"),
            Self::Question => f.write_str("?"),
            Self::EqEq => f.write_str("=="),
            Self::EqEqEq => f.write_str("==="),
            Self::BangEq => f.write_str("!="),
            Self::BangEqEq => f.write_str("!=="),
            Self::Lt => f.write_str("<"),
            Self::LtEq => f.write_str("<="),
            Self::Gt => f.write_str(">"),
            Self::GtEq => f.write_str(">="),
            Self::Plus => f.write_str("+"),
            Self::Minus => f.write_str("-"),
            Self::Star => f.write_str("*"),
            Self::Slash => f.write_str("/"),
            Self::Percent => f.write_str("%"),
            Self::StarStar => f.write_str("**"),
            Self::PlusPlus => f.write_str("++"),
            Self::MinusMinus => f.write_str("--"),
            Self::Amp => f.write_str("&"),
            Self::Pipe => f.write_str("|"),
            Self::Caret => f.write_str("^"),
            Self::Tilde => f.write_str("~"),
            Self::LtLt => f.write_str("<<"),
            Self::GtGt => f.write_str(">>"),
            Self::GtGtGt => f.write_str(">>>"),
            Self::AmpAmp => f.write_str("&&"),
            Self::PipePipe => f.write_str("||"),
            Self::QQ => f.write_str("??"),
            Self::Bang => f.write_str("!"),
            Self::Eq => f.write_str("="),
            Self::PlusEq => f.write_str("+="),
            Self::MinusEq => f.write_str("-="),
            Self::StarEq => f.write_str("*="),
            Self::SlashEq => f.write_str("/="),
            Self::PercentEq => f.write_str("%="),
            Self::StarStarEq => f.write_str("**="),
            Self::LtLtEq => f.write_str("<<="),
            Self::GtGtEq => f.write_str(">>="),
            Self::GtGtGtEq => f.write_str(">>>="),
            Self::AmpEq => f.write_str("&="),
            Self::PipeEq => f.write_str("|="),
            Self::CaretEq => f.write_str("^="),
            Self::AmpAmpEq => f.write_str("&&="),
            Self::PipePipeEq => f.write_str("||="),
            Self::QQEq => f.write_str("??="),
            Self::Eof => f.write_str("<eof>"),
        }
    }
}
