use crate::{Token, TokenKind};
use std::{iter::Peekable, vec::IntoIter};

/// Cursor for tokens in a token sequence, providing direct value access and advanced peeking.
#[derive(Debug)]
pub struct Cursor {
    tokens: Peekable<IntoIter<Token>>,
    value: Option<Token>,
}

impl Cursor {
    /// Create a new token cursor.
    ///
    /// The cursor is initialized with the first token of the sequence.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Location;
    /// use nexus_lang::parser::cursor::Cursor;
    /// use nexus_lang::scanner::{Token, TokenKind};
    ///
    /// let tokens = vec![
    ///     Token::new(TokenKind::Number(1.0), "1", Location::new(0, 1, 1)),
    ///     Token::new(TokenKind::Plus, "+", Location::new(0, 1, 3)),
    ///     Token::new(TokenKind::Number(2.0), "2", Location::new(0, 1, 5)),
    /// ];
    /// let cursor = Cursor::new(tokens);
    ///
    /// assert_eq!(
    ///     cursor.value(),
    ///     Some(&Token::new(TokenKind::Number(1.0), "1", Location::new(0, 1, 1)))
    /// );
    /// ```
    #[must_use]
    pub fn new(tokens: Vec<Token>) -> Self {
        let mut cursor = Cursor {
            tokens: tokens.into_iter().peekable(),
            value: None,
        };

        cursor.advance();
        cursor
    }

    /// Get the current cursor value.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Location;
    /// use nexus_lang::parser::cursor::Cursor;
    /// use nexus_lang::scanner::{Token, TokenKind};
    ///
    /// let tokens = vec![
    ///     Token::new(TokenKind::Number(1.0), "1", Location::new(0, 1, 1)),
    ///     Token::new(TokenKind::Plus, "+", Location::new(0, 1, 3)),
    ///     Token::new(TokenKind::Number(2.0), "2", Location::new(0, 1, 5)),
    /// ];
    /// let mut cursor = Cursor::new(tokens);
    ///
    /// assert_eq!(
    ///     cursor.value(),
    ///     Some(&Token::new(TokenKind::Number(1.0), "1", Location::new(0, 1, 1)))
    /// );
    /// ```
    #[must_use]
    pub fn value(&self) -> Option<&Token> {
        self.value.as_ref()
    }

    /// Check if the current token matches one of a list of kinds.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Location;
    /// use nexus_lang::parser::cursor::Cursor;
    /// use nexus_lang::scanner::{Token, TokenKind};
    ///
    /// let tokens = vec![
    ///     Token::new(TokenKind::Plus, "+", Location::new(0, 1, 1)),
    ///     Token::new(TokenKind::Minus, "-", Location::new(0, 1, 2)),
    /// ];
    /// let mut cursor = Cursor::new(tokens);
    ///
    /// assert_eq!(cursor.matches(&[TokenKind::Minus]), false);
    /// assert_eq!(cursor.matches(&[TokenKind::Plus]), true);
    /// cursor.advance();
    /// assert_eq!(cursor.matches(&[TokenKind::Minus]), true);
    /// ```
    pub fn matches(&mut self, kinds: &[TokenKind]) -> bool {
        self.value.as_ref().is_some_and(|t| kinds.contains(&t.kind))
    }

    /// Peek at the next token (one past the current position) without advancing.
    ///
    /// Returns `None` if there is no next token.
    ///
    #[must_use]
    pub fn peek(&mut self) -> Option<&Token> {
        self.tokens.peek()
    }

    /// Advance the cursor to the next token.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Location;
    /// use nexus_lang::parser::cursor::Cursor;
    /// use nexus_lang::scanner::{Token, TokenKind};
    ///
    /// let tokens = vec![
    ///     Token::new(TokenKind::Number(1.0), "1", Location::new(0, 1, 1)),
    ///     Token::new(TokenKind::Plus, "+", Location::new(0, 1, 3)),
    ///     Token::new(TokenKind::Number(2.0), "2", Location::new(0, 1, 5)),
    /// ];
    /// let mut cursor = Cursor::new(tokens);
    ///
    /// assert_eq!(
    ///     cursor.value(),
    ///     Some(&Token::new(TokenKind::Number(1.0), "1", Location::new(0, 1, 1)))
    /// );
    /// assert_eq!(
    ///     cursor.advance(),
    ///     Some(&Token::new(TokenKind::Plus, "+", Location::new(0, 1, 3)))
    /// );
    /// assert_eq!(
    ///     cursor.advance(),
    ///     Some(&Token::new(TokenKind::Number(2.0), "2", Location::new(0, 1, 5)))
    /// );
    /// assert_eq!(
    ///     cursor.advance(),
    ///     None
    /// );
    /// ```
    pub fn advance(&mut self) -> Option<&Token> {
        self.value = self.tokens.next();
        self.value.as_ref()
    }

    /// Conditionally advance the cursor if the current token matches one of a list of kinds.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Location;
    /// use nexus_lang::parser::cursor::Cursor;
    /// use nexus_lang::scanner::{Token, TokenKind};
    ///
    /// let tokens = vec![
    ///     Token::new(TokenKind::Plus, "+", Location::new(0, 1, 1)),
    ///     Token::new(TokenKind::Minus, "-", Location::new(0, 1, 3)),
    ///     Token::new(TokenKind::Star, "*", Location::new(0, 1, 5)),
    /// ];
    /// let mut cursor = Cursor::new(tokens);
    ///
    /// assert_eq!(cursor.advance_if_matches(&[TokenKind::Minus]), false);
    /// assert_eq!(cursor.advance_if_matches(&[TokenKind::Plus]), true);
    /// assert_eq!(cursor.advance_if_matches(&[TokenKind::Minus]), true);
    /// assert_eq!(cursor.advance_if_matches(&[TokenKind::Dot]), false);
    /// assert_eq!(cursor.advance_if_matches(&[TokenKind::Star]), true);
    /// ```
    pub fn advance_if_matches(&mut self, kinds: &[TokenKind]) -> bool {
        self.value
            .as_ref()
            .is_some_and(|t| kinds.contains(&t.kind))
            .then(|| self.advance())
            .is_some()
    }

    /// Check current token kind and consume current token.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Location;
    /// use nexus_lang::parser::cursor::Cursor;
    /// use nexus_lang::scanner::{Token, TokenKind};
    ///
    /// let tokens = vec![
    ///     Token::new(TokenKind::Number(1.0), "1", Location::new(0, 1, 1)),
    ///     Token::new(TokenKind::Plus, "+", Location::new(0, 1, 3)),
    ///     Token::new(TokenKind::Number(2.0), "2", Location::new(0, 1, 5)),
    /// ];
    /// let mut cursor = Cursor::new(tokens);
    ///
    /// assert_eq!(cursor.consume(TokenKind::Plus), false);
    /// assert_eq!(cursor.consume(TokenKind::Number(1.0)), true);
    /// assert_eq!(cursor.consume(TokenKind::Plus), true);
    /// assert_eq!(cursor.consume(TokenKind::Number(2.0)), true);
    /// assert_eq!(cursor.consume(TokenKind::Plus), false);
    /// ```
    #[allow(clippy::needless_pass_by_value)]
    pub fn consume(&mut self, kind: TokenKind) -> bool {
        self.value
            .as_ref()
            .is_some_and(|t| t.kind == kind)
            .then(|| self.advance())
            .is_some()
    }

    /// Check if cursor is at end-of-stream (EOS).
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Location;
    /// use nexus_lang::parser::cursor::Cursor;
    /// use nexus_lang::scanner::{Token, TokenKind};
    ///
    /// let tokens = vec![Token::new(TokenKind::Number(1.0), "1", Location::new(0, 1, 1))];
    /// let mut cursor = Cursor::new(tokens);
    ///
    /// assert_eq!(cursor.is_eos(), false);
    /// cursor.advance();
    /// assert_eq!(cursor.is_eos(), true);
    /// ```
    #[must_use]
    pub fn is_eos(&self) -> bool {
        self.value.is_none()
    }
}
