use std::{fmt::Display, ops::Range};

/// A half-open interval `[start, end)` representing a location in the source text.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {
    pub start: usize,
    pub end: usize,
}

impl Span {
    pub const EOF: Self = Self::new(0, 0);

    /// Create a new [Span] from the start and end positions.
    pub const fn new(start: usize, end: usize) -> Self {
        debug_assert!(start <= end);

        Self { start, end }
    }

    /// Create a new extended [Span] which contains both `self` and `other`.
    pub fn join(self, other: Self) -> Self {
        Self::new(self.start.min(other.start), self.end.max(other.end))
    }
}

impl From<Span> for Range<usize> {
    fn from(value: Span) -> Self {
        value.start..value.end
    }
}

/// A fundamental, categorized unit of source code produced during lexical analysis.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Token<'src> {
    pub kind: TokenKind,
    pub text: &'src str,
    pub span: Span,
}

impl<'src> Token<'src> {
    /// Create a new [Token] from kind, source text and [Span].
    pub const fn new(kind: TokenKind, text: &'src str, span: Span) -> Self {
        Self { kind, text, span }
    }
}

/// An enum representing all possible kinds of [Token]s.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenKind {
    Invalid,

    // Literals
    Identifier,
    IntegerLit,
    BooleanLit,
    FloatLit,

    // Keywords
    Fn,
    Foreign,
    If,
    As,
    Else,
    Return,
    Let,
    While,
    Struct,
    Break,
    Continue,

    // Types
    I8,
    I16,
    I32,
    I64,
    U8,
    U16,
    U32,
    U64,
    F32,
    F64,
    Bool,

    // Operators
    Plus,
    Minus,
    Star,
    Slash,
    Percent,
    Ampersand,

    Equal,
    Unequal,
    LessThan,
    LessEqual,
    GreaterThan,
    GreaterEqual,

    Assign,
    Bang,

    // Punctuation
    Dot,
    Comma,
    Colon,
    Semicolon,
    Arrow,

    // Parentheses
    LParen,
    RParen,
    LBrace,
    RBrace,
    LBracket,
    RBracket,
}

impl Display for TokenKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            TokenKind::Invalid => "an invalid token",
            TokenKind::Identifier => "an identifier",
            TokenKind::IntegerLit => "an integer",
            TokenKind::BooleanLit => "a boolean",
            TokenKind::FloatLit => "a float",
            TokenKind::Fn => "`fn`",
            TokenKind::Foreign => "`foreign`",
            TokenKind::As => "`as`",
            TokenKind::If => "`if`",
            TokenKind::Else => "`else`",
            TokenKind::Return => "`return`",
            TokenKind::Let => "`let`",
            TokenKind::While => "`while`",
            TokenKind::Struct => "`struct`",
            TokenKind::Break => "`break`",
            TokenKind::Continue => "`continue`",
            TokenKind::I8 => "`i8`",
            TokenKind::I16 => "`i16`",
            TokenKind::I32 => "`i32`",
            TokenKind::I64 => "`i64`",
            TokenKind::U8 => "`u8`",
            TokenKind::U16 => "`u16`",
            TokenKind::U32 => "`u32`",
            TokenKind::U64 => "`u64`",
            TokenKind::F32 => "`f32`",
            TokenKind::F64 => "`f64`",
            TokenKind::Bool => "`bool`",
            TokenKind::Plus => "`+`",
            TokenKind::Minus => "`-`",
            TokenKind::Star => "`*`",
            TokenKind::Slash => "`/`",
            TokenKind::Percent => "`%`",
            TokenKind::Ampersand => "`&`",
            TokenKind::Equal => "`==`",
            TokenKind::Unequal => "`!=`",
            TokenKind::LessThan => "`<`",
            TokenKind::LessEqual => "`<=`",
            TokenKind::GreaterThan => "`>`",
            TokenKind::GreaterEqual => "`>=`",
            TokenKind::Assign => "`=`",
            TokenKind::Bang => "`!`",
            TokenKind::Dot => "`.`",
            TokenKind::Comma => "`,`",
            TokenKind::Colon => "`:`",
            TokenKind::Semicolon => "`;`",
            TokenKind::Arrow => "`->`",
            TokenKind::LParen => "`(`",
            TokenKind::RParen => "`)`",
            TokenKind::LBrace => "`{`",
            TokenKind::RBrace => "`}`",
            TokenKind::LBracket => "`[`",
            TokenKind::RBracket => "`]`",
        })
    }
}
