/// A text buffer with cursor position management for filter editing.
#[derive(Debug, Clone)]
pub struct TextBuffer {
    text: String,
    cursor: usize, // char position (not byte position)
}

impl TextBuffer {
    pub fn new(initial: &str) -> Self {
        let cursor = initial.chars().count();
        Self {
            text: initial.to_string(),
            cursor,
        }
    }

    pub fn text(&self) -> &str {
        &self.text
    }

    #[cfg(test)]
    pub fn cursor(&self) -> usize {
        self.cursor
    }

    #[cfg(test)]
    pub fn set_cursor(&mut self, pos: usize) {
        self.cursor = pos.min(self.len());
    }

    pub fn len(&self) -> usize {
        self.text.chars().count()
    }

    /// Insert a character at the current cursor position.
    pub fn insert(&mut self, c: char) {
        let byte_pos = self.cursor_byte_pos();
        self.text.insert(byte_pos, c);
        self.cursor += 1;
    }

    /// Delete the character before the cursor. Returns true if a character was deleted.
    pub fn backspace(&mut self) -> bool {
        if self.cursor > 0 {
            self.cursor -= 1;
            let byte_pos = self.cursor_byte_pos();
            self.text.remove(byte_pos);
            true
        } else {
            false
        }
    }

    /// Delete the character at the cursor. Returns true if a character was deleted.
    pub fn delete(&mut self) -> bool {
        if self.cursor < self.len() {
            let byte_pos = self.cursor_byte_pos();
            self.text.remove(byte_pos);
            true
        } else {
            false
        }
    }

    /// Move cursor one character left.
    pub fn move_left(&mut self) {
        self.cursor = self.cursor.saturating_sub(1);
    }

    /// Move cursor one character right.
    pub fn move_right(&mut self) {
        self.cursor = (self.cursor + 1).min(self.len());
    }

    /// Move cursor to start of text.
    pub fn move_to_start(&mut self) {
        self.cursor = 0;
    }

    /// Move cursor to end of text.
    pub fn move_to_end(&mut self) {
        self.cursor = self.len();
    }

    /// Convert char position to byte index in the underlying string.
    pub fn cursor_byte_pos(&self) -> usize {
        self.char_to_byte_index(self.cursor)
    }

    fn char_to_byte_index(&self, char_index: usize) -> usize {
        self.text
            .char_indices()
            .nth(char_index)
            .map(|(i, _)| i)
            .unwrap_or(self.text.len())
    }

    /// Delete the word before the cursor (Ctrl+W behavior).
    /// Treats `.` and spaces as word boundaries.
    pub fn delete_word_back(&mut self) -> bool {
        if self.cursor == 0 {
            return false;
        }

        let chars: Vec<char> = self.text.chars().collect();
        let mut new_cursor = self.cursor;

        // Skip trailing whitespace/dots
        while new_cursor > 0 && (chars[new_cursor - 1] == '.' || chars[new_cursor - 1] == ' ') {
            new_cursor -= 1;
        }

        // Delete back to previous dot, space, or start
        while new_cursor > 0 && chars[new_cursor - 1] != '.' && chars[new_cursor - 1] != ' ' {
            new_cursor -= 1;
        }

        if new_cursor < self.cursor {
            let start_byte = self.char_to_byte_index(new_cursor);
            let end_byte = self.char_to_byte_index(self.cursor);
            self.text.replace_range(start_byte..end_byte, "");
            self.cursor = new_cursor;
            true
        } else {
            false
        }
    }

    /// Get valid cursor positions for word-based navigation.
    /// Positions are after `.`, after `]`, or after identifiers.
    pub fn navigation_positions(&self) -> Vec<usize> {
        Self::compute_navigation_positions(&self.text)
    }

    fn compute_navigation_positions(filter_text: &str) -> Vec<usize> {
        let mut positions = vec![0];
        let mut in_string = false;
        let mut prev_char = ' ';
        let chars: Vec<char> = filter_text.chars().collect();
        let len = chars.len();

        for (i, &c) in chars.iter().enumerate() {
            if c == '"' && prev_char != '\\' {
                in_string = !in_string;
            }

            if !in_string {
                // After ']' (end of bracket expression)
                if prev_char == ']' && !positions.contains(&i) {
                    positions.push(i);
                }
                // At '|' (before pipe)
                if c == '|' && !positions.contains(&i) {
                    positions.push(i);
                }
                // After '|' and whitespace (start of next segment)
                if (prev_char == '|' || (prev_char == ' ' && i > 1 && chars[i - 2] == '|'))
                    && c != ' '
                    && !positions.contains(&i)
                {
                    positions.push(i);
                }
                // After identifier following '.'
                if i > 0 && !c.is_alphanumeric() && c != '_' {
                    // Check if previous chars were an identifier after '.'
                    let mut j = i;
                    while j > 0 && (chars[j - 1].is_alphanumeric() || chars[j - 1] == '_') {
                        j -= 1;
                    }
                    if j > 0 && chars[j - 1] == '.' && j < i && !positions.contains(&i) {
                        positions.push(i);
                    }
                }
            }
            prev_char = c;
        }

        // Add end position based on what the filter ends with
        if len > 0 && !positions.contains(&len) {
            let last = chars.last().copied().unwrap_or(' ');
            // Identity (just "."), ends with ], or ends with identifier
            if (len == 1 && last == '.') || last == ']' || last.is_alphanumeric() || last == '_' {
                positions.push(len);
            }
        }

        // Also add position 1 if filter starts with '.' and len > 1 (after initial dot)
        if len > 1 && filter_text.starts_with('.') && !positions.contains(&1) {
            positions.push(1);
        }

        positions.sort();
        positions.dedup();
        positions
    }

    /// Jump cursor to the previous word boundary.
    pub fn jump_word_back(&mut self) {
        if self.cursor == 0 {
            return;
        }

        let positions = self.navigation_positions();

        // Find largest position less than current cursor
        let mut target = 0;
        for &pos in &positions {
            if pos < self.cursor {
                target = pos;
            }
        }

        self.cursor = target;
    }

    /// Jump cursor to the next word boundary.
    pub fn jump_word_forward(&mut self) {
        let len = self.len();
        if self.cursor >= len {
            return;
        }

        let positions = self.navigation_positions();

        // Find smallest position greater than current cursor
        for &pos in &positions {
            if pos > self.cursor {
                self.cursor = pos;
                return;
            }
        }

        self.cursor = len;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new() {
        let buf = TextBuffer::new("hello");
        assert_eq!(buf.text(), "hello");
        assert_eq!(buf.cursor(), 5);
        assert_eq!(buf.len(), 5);
    }

    #[test]
    fn test_insert() {
        let mut buf = TextBuffer::new(".");
        buf.insert('f');
        assert_eq!(buf.text(), ".f");
        buf.insert('o');
        buf.insert('o');
        assert_eq!(buf.text(), ".foo");
        assert_eq!(buf.cursor(), 4);
    }

    #[test]
    fn test_backspace() {
        let mut buf = TextBuffer::new(".foo");
        assert!(buf.backspace());
        assert_eq!(buf.text(), ".fo");
        assert_eq!(buf.cursor(), 3);
    }

    #[test]
    fn test_backspace_at_start() {
        let mut buf = TextBuffer::new(".");
        buf.set_cursor(0);
        assert!(!buf.backspace());
        assert_eq!(buf.text(), ".");
    }

    #[test]
    fn test_delete() {
        let mut buf = TextBuffer::new(".foo");
        buf.set_cursor(1);
        assert!(buf.delete());
        assert_eq!(buf.text(), ".oo");
        assert_eq!(buf.cursor(), 1);
    }

    #[test]
    fn test_delete_at_end() {
        let mut buf = TextBuffer::new(".foo");
        assert!(!buf.delete());
        assert_eq!(buf.text(), ".foo");
    }

    #[test]
    fn test_move_left_right() {
        let mut buf = TextBuffer::new(".foo");
        assert_eq!(buf.cursor(), 4);
        buf.move_left();
        assert_eq!(buf.cursor(), 3);
        buf.move_right();
        assert_eq!(buf.cursor(), 4);
        buf.move_right(); // Should not go past end
        assert_eq!(buf.cursor(), 4);
    }

    #[test]
    fn test_move_to_start_end() {
        let mut buf = TextBuffer::new(".foo");
        buf.move_to_start();
        assert_eq!(buf.cursor(), 0);
        buf.move_to_end();
        assert_eq!(buf.cursor(), 4);
    }

    #[test]
    fn test_delete_word_back() {
        let mut buf = TextBuffer::new(".foo.bar");
        assert!(buf.delete_word_back());
        assert_eq!(buf.text(), ".foo.");
        assert_eq!(buf.cursor(), 5);
    }

    #[test]
    fn test_delete_word_back_at_start() {
        let mut buf = TextBuffer::new(".");
        buf.set_cursor(0);
        assert!(!buf.delete_word_back());
        assert_eq!(buf.text(), ".");
    }

    #[test]
    fn test_navigation_positions_identity() {
        let buf = TextBuffer::new(".");
        assert_eq!(buf.navigation_positions(), vec![0, 1]);
    }

    #[test]
    fn test_navigation_positions_field() {
        let buf = TextBuffer::new(".foo");
        assert_eq!(buf.navigation_positions(), vec![0, 1, 4]);
    }

    #[test]
    fn test_navigation_positions_iterate() {
        let buf = TextBuffer::new(".[]");
        assert_eq!(buf.navigation_positions(), vec![0, 1, 3]);
    }

    #[test]
    fn test_navigation_positions_multiple_iterate() {
        let buf = TextBuffer::new(".[][][]");
        assert_eq!(buf.navigation_positions(), vec![0, 1, 3, 5, 7]);
    }

    #[test]
    fn test_navigation_positions_index() {
        let buf = TextBuffer::new(".[0]");
        assert_eq!(buf.navigation_positions(), vec![0, 1, 4]);
    }

    #[test]
    fn test_navigation_positions_field_chain() {
        let buf = TextBuffer::new(".foo.bar");
        assert_eq!(buf.navigation_positions(), vec![0, 1, 4, 8]);
    }

    #[test]
    fn test_navigation_positions_mixed() {
        let buf = TextBuffer::new(".foo[0].bar");
        assert_eq!(buf.navigation_positions(), vec![0, 1, 4, 7, 11]);
    }

    #[test]
    fn test_navigation_positions_quoted_field() {
        let buf = TextBuffer::new(".[\"foo\"]");
        assert_eq!(buf.navigation_positions(), vec![0, 1, 8]);
    }

    #[test]
    fn test_jump_word_back() {
        let mut buf = TextBuffer::new(".foo.bar");
        buf.jump_word_back();
        assert_eq!(buf.cursor(), 4);
        buf.jump_word_back();
        assert_eq!(buf.cursor(), 1);
        buf.jump_word_back();
        assert_eq!(buf.cursor(), 0);
    }

    #[test]
    fn test_jump_word_forward() {
        let mut buf = TextBuffer::new(".foo.bar");
        buf.set_cursor(0);
        buf.jump_word_forward();
        assert_eq!(buf.cursor(), 1);
        buf.jump_word_forward();
        assert_eq!(buf.cursor(), 4);
        buf.jump_word_forward();
        assert_eq!(buf.cursor(), 8);
    }

    #[test]
    fn test_unicode_handling() {
        let mut buf = TextBuffer::new(".héllo");
        assert_eq!(buf.len(), 6);
        assert_eq!(buf.cursor(), 6);
        buf.move_left();
        assert_eq!(buf.cursor(), 5);
        buf.insert('!');
        assert_eq!(buf.text(), ".héll!o");
    }

    #[test]
    fn test_navigation_positions_with_pipes() {
        // ". | sort | reverse"
        // 0  12 4    9 11
        let buf = TextBuffer::new(". | sort | reverse");
        let positions = buf.navigation_positions();
        assert!(positions.contains(&0)); // start
        assert!(positions.contains(&1)); // after .
        assert!(positions.contains(&2)); // at first |
        assert!(positions.contains(&4)); // start of sort
        assert!(positions.contains(&9)); // at second |
        assert!(positions.contains(&11)); // start of reverse
        assert!(positions.contains(&18)); // end
    }

    #[test]
    fn test_jump_word_back_with_pipes() {
        let mut buf = TextBuffer::new(". | sort | reverse");
        // cursor at end (18)
        buf.jump_word_back();
        assert_eq!(buf.cursor(), 11); // start of reverse
        buf.jump_word_back();
        assert_eq!(buf.cursor(), 9); // at second |
        buf.jump_word_back();
        assert_eq!(buf.cursor(), 4); // start of sort
        buf.jump_word_back();
        assert_eq!(buf.cursor(), 2); // at first |
        buf.jump_word_back();
        assert_eq!(buf.cursor(), 1); // after .
        buf.jump_word_back();
        assert_eq!(buf.cursor(), 0); // start
    }
}
