use crate::Location;
use std::{iter::Peekable, str::Chars};

/// Cursor for characters in a string, providing direct value access and advanced peeking.
#[derive(Debug)]
pub struct Cursor<'a> {
    chars: Peekable<Chars<'a>>,
    location: Location,
    value: Option<char>,
}

impl<'a> Cursor<'a> {
    /// Create a new cursor given a string.
    ///
    /// The cursor is initialized with the first character of the string.
    #[must_use]
    pub fn new(line: &'a str) -> Self {
        let mut chars = line.chars();
        let value = chars.next();

        Cursor {
            chars: chars.peekable(),
            location: Location::default(),
            value,
        }
    }

    /// Get the value of the current character the cursor is pointing at (if any).
    #[must_use]
    pub fn value(&self) -> Option<char> {
        self.value
    }

    /// Advance the cursor one location, replacing the inner value.
    pub fn advance(&mut self) {
        self.index_inc();
        self.next();
    }

    /// Advance the cursor while a predicate holds, consuming values at each increment.
    pub fn advance_while(&mut self, mut predicate: impl FnMut(char) -> bool) {
        while let Some(c) = self.value {
            if !predicate(c) {
                break;
            }

            self.index_inc();
            self.next();
        }
    }

    /// Peek into the next character without consuming the current value.
    #[must_use]
    pub fn peek(&self) -> Option<char> {
        self.chars.clone().peek().copied()
    }

    /// Get the current location of the cursor.
    #[must_use]
    pub fn location(&self) -> Location {
        self.location
    }

    /// Get the current character index of the cursor.
    #[must_use]
    pub fn index(&self) -> usize {
        self.location.index
    }

    /// Consume character and update the cursor state.
    fn next(&mut self) {
        self.value = self.chars.next();
    }

    /// Increment index.
    fn index_inc(&mut self) {
        if let Some(c) = self.value {
            // Use byte width so that location.index is always a valid UTF-8 byte offset,
            // which is required for safe &str slicing in the scanner.
            self.location.index += c.len_utf8();

            if c == '\n' {
                self.location.line += 1;
                self.location.column = 1;
            } else {
                self.location.column += 1;
            }
        }
    }

    /// Advance and return the current character.
    ///
    /// This combines `value()` and `advance()` into a single operation.
    pub fn consume(&mut self) -> Option<char> {
        let current = self.value;
        self.advance();
        current
    }
}

impl Iterator for Cursor<'_> {
    type Item = char;

    /// Advance the cursor and return the current character.
    ///
    /// This allows the cursor to be used in iterator contexts, making it compatible with standard iterator methods.
    fn next(&mut self) -> Option<Self::Item> {
        self.consume()
    }
}

#[cfg(test)]
mod tests {
    use super::Cursor;
    use crate::Location;
    use pretty_assertions::assert_eq;

    #[test]
    fn new() {
        let empty = String::new();
        let line = "Test123".to_string();

        let c1 = Cursor::new(&empty);
        let c2 = Cursor::new(&line);

        assert_eq!(c1.value(), None);
        assert_eq!(c2.value(), Some('T'));
    }

    #[test]
    fn advance() {
        let line = "abcdefg".to_string();

        let mut c = Cursor::new(&line);

        for i in "abcdefg".chars() {
            assert_eq!(c.value().unwrap(), i);
            c.advance();
        }

        assert!(c.value().is_none());
    }

    #[test]
    fn advance_while() {
        let line = "abcd0123".to_string();

        let mut c = Cursor::new(&line);

        assert_eq!(c.value(), Some('a'));
        c.advance_while(char::is_numeric); // No advancement.
        assert_eq!(c.value(), Some('a'));
        c.advance_while(char::is_alphabetic);
        assert_eq!(c.value(), Some('0'));
        c.advance_while(char::is_numeric);
        assert_eq!(c.value(), None);

        c.advance();
        assert!(c.value().is_none());

        // Test advance_while that breaks on predicate failure.
        let mixed = "abc123xyz".to_string();
        let mut c = Cursor::new(&mixed);
        c.advance_while(char::is_alphabetic);
        assert_eq!(c.value(), Some('1'));

        // Consume remaining characters.
        c.advance_while(char::is_alphanumeric);
        assert_eq!(c.value(), None);
    }

    #[test]
    fn peek() {
        let line = "abcdefg".to_string();

        let mut c = Cursor::new(&line);

        for i in "bcdefg".chars() {
            assert_eq!(c.peek().unwrap(), i);
            c.advance();
        }

        assert!(c.value().is_some());

        c.advance();

        assert!(c.value().is_none());
    }

    #[test]
    fn index_advance() {
        let mut cursor = Cursor::new("0123456789");

        assert_eq!(cursor.index(), 0);

        while cursor.value().is_some() {
            assert_eq!(
                cursor.index(),
                cursor.value().unwrap().to_digit(10).unwrap() as usize
            );
            cursor.advance();
        }

        assert_eq!(cursor.index(), 10);

        cursor.advance();
        cursor.advance();

        assert!(cursor.value().is_none());
        assert_eq!(cursor.index(), 10);
    }

    #[test]
    fn location() {
        let s = "abc123\nblah".to_string();
        let mut c = Cursor::new(&s);

        assert_eq!(c.location(), Location::new(0, 1, 1));
        c.advance();
        assert_eq!(c.location(), Location::new(1, 1, 2));
        c.advance();
        c.advance();
        c.advance();
        assert_eq!(c.location(), Location::new(4, 1, 5));
        c.advance();
        c.advance();
        c.advance();
        c.advance();
        assert_eq!(c.location(), Location::new(8, 2, 2));
    }

    #[test]
    fn consume() {
        let s = "abc".to_string();
        let mut c = Cursor::new(&s);

        assert_eq!(c.consume(), Some('a'));
        assert_eq!(c.consume(), Some('b'));
        assert_eq!(c.consume(), Some('c'));
        assert_eq!(c.consume(), None);
        assert!(c.value().is_none());

        // Test consume on empty string.
        let empty = String::new();
        let mut c = Cursor::new(&empty);
        assert_eq!(c.consume(), None);
        assert!(c.value().is_none());
    }

    #[test]
    fn iterator_implementation() {
        let s = "abc".to_string();
        let c = Cursor::new(&s);

        let chars: Vec<char> = c.collect();
        assert_eq!(chars, vec!['a', 'b', 'c']);

        // Test iterator with empty string.
        let empty = String::new();
        let c = Cursor::new(&empty);
        let chars: Vec<char> = c.collect();
        assert_eq!(chars, Vec::<char>::new());
    }

    #[test]
    fn iterator_with_for_loop() {
        let s = "hello".to_string();
        let c = Cursor::new(&s);

        let mut result = String::new();
        for ch in c {
            result.push(ch);
        }
        assert_eq!(result, "hello");
    }

    #[test]
    fn iterator_partial_consumption() {
        let s = "abcdef".to_string();
        let mut c = Cursor::new(&s);

        // Consume first two characters manually.
        assert_eq!(c.consume(), Some('a'));
        assert_eq!(c.consume(), Some('b'));

        // Use iterator for the rest.
        let remaining: Vec<char> = c.collect();
        assert_eq!(remaining, vec!['c', 'd', 'e', 'f']);
    }
}
