//! Find references provider
//!
//! Finds all references to a symbol across the document

use tower_lsp::lsp_types::{
    Location,
    Range,
    ReferenceParams,
    Url,
};

use super::document::Document;

/// Find all references to a symbol at a position
pub fn find_references(
    doc: &Document,
    params: &ReferenceParams,
    uri: &Url,
) -> Option<Vec<Location>> {
    let position = params.text_document_position.position;

    // Convert LSP position to byte offset
    let offset = position_to_offset(doc, position.line as usize, position.character as usize)?;

    // Get the word at the cursor
    let word = doc.word_at_offset(offset)?;

    // Search for all occurrences of this word in the text
    let mut locations = Vec::new();
    let mut positions = doc.positions.clone();

    for (byte_offset, _) in doc.text.match_indices(&word) {
        // Check if this is a word boundary match
        if is_word_boundary(&doc.text, byte_offset, word.len()) {
            let (start_line, start_col) = positions.offset_to_position(byte_offset);
            let (end_line, end_col) = positions.offset_to_position(byte_offset + word.len());

            locations.push(Location {
                uri: uri.clone(),
                range: Range {
                    start: tower_lsp::lsp_types::Position {
                        line: start_line as u32,
                        character: start_col as u32,
                    },
                    end: tower_lsp::lsp_types::Position {
                        line: end_line as u32,
                        character: end_col as u32,
                    },
                },
            });
        }
    }

    if locations.is_empty() {
        None
    } else {
        Some(locations)
    }
}

/// Convert LSP position to byte offset
fn position_to_offset(doc: &Document, line: usize, character: usize) -> Option<usize> {
    let line_start = doc.positions.line_offset(line)?;
    Some(line_start + character)
}

/// Check if a match is at a word boundary
fn is_word_boundary(text: &str, offset: usize, len: usize) -> bool {
    let before_ok = offset == 0 || !is_word_char_at(text, offset - 1);
    let after_ok = offset + len >= text.len() || !is_word_char_at(text, offset + len);
    before_ok && after_ok
}

fn is_word_char_at(text: &str, offset: usize) -> bool {
    text.chars()
        .nth(offset)
        .is_some_and(|c| c.is_alphanumeric() || c == '_')
}
