//! Document state management for LSP
//!
//! This module manages parsed document state including AST, position tracking,
//! and symbol tables for efficient LSP operations.

use crate::{
    position::PositionTracker,
    resolve::{
        names::NameTable,
        validate,
        ErrorCollector,
        ResolveError,
    },
    syntax::{
        ast::File,
        lexer::Lexer,
        FileParser,
    },
};

/// Parsed document with all necessary state for LSP operations
pub struct Document {
    /// The source text
    pub text: String,
    /// Position tracker for offset -> line/col conversion
    pub positions: PositionTracker,
    /// Parsed AST (if parsing succeeded)
    pub ast: Option<File>,
    /// Parse errors
    pub parse_errors: Vec<ParseError>,
    /// Semantic validation errors from resolver
    pub resolve_errors: Vec<ResolveError>,
    /// Name table from the language's semantic resolution
    pub name_table: NameTable,
}

#[derive(Debug, Clone)]
pub struct ParseError {
    pub start: usize,
    pub end: usize,
    pub message: String,
    pub severity: ErrorSeverity,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorSeverity {
    Error,
    Warning,
}

impl Document {
    /// Create a new document from source text
    pub fn new(text: String) -> Self {
        let positions = PositionTracker::new(&text);

        // Parse the document
        let (ast, parse_errors) = Self::parse(&text);

        // Build name table and run semantic validation
        let (name_table, resolve_errors) = if let Some(ref ast) = ast {
            // Use language's NameTable for semantic resolution
            let mut resolve_errors = Vec::new();
            let name_table = match NameTable::from_file(ast) {
                | Ok(table) => table,
                | Err(e) => {
                    // Capture NameTable errors (e.g., duplicate definitions)
                    resolve_errors.push(e);
                    NameTable::new()
                },
            };
            // Also run validation
            resolve_errors.extend(Self::validate(ast));
            (name_table, resolve_errors)
        } else {
            (NameTable::new(), Vec::new())
        };

        Self {
            text,
            positions,
            ast,
            parse_errors,
            resolve_errors,
            name_table,
        }
    }

    /// Update the document with new text
    pub fn update(&mut self, text: String) {
        self.text = text;
        self.positions = PositionTracker::new(&self.text);

        let (ast, parse_errors) = Self::parse(&self.text);
        self.ast = ast;
        self.parse_errors = parse_errors;

        let (name_table, resolve_errors) = if let Some(ref ast) = self.ast {
            // Use language's NameTable for semantic resolution
            let mut resolve_errors = Vec::new();
            let name_table = match NameTable::from_file(ast) {
                | Ok(table) => table,
                | Err(e) => {
                    // Capture NameTable errors (e.g., duplicate definitions)
                    resolve_errors.push(e);
                    NameTable::new()
                },
            };
            // Also run validation
            resolve_errors.extend(Self::validate(ast));
            (name_table, resolve_errors)
        } else {
            (NameTable::new(), Vec::new())
        };

        self.name_table = name_table;
        self.resolve_errors = resolve_errors;
    }

    /// Run semantic validation on the AST
    fn validate(ast: &File) -> Vec<ResolveError> {
        let mut collector = ErrorCollector::default();

        // Validate each declaration type
        for decl in &ast.declarations {
            use crate::syntax::ast::Declaration;

            match decl {
                | Declaration::Character(c) => {
                    validate::validate_no_reserved_keywords(&c.fields, &mut collector);
                    validate::validate_trait_ranges(&c.fields, &mut collector);
                },
                | Declaration::Template(t) => {
                    validate::validate_no_reserved_keywords(&t.fields, &mut collector);
                },
                | Declaration::LifeArc(l) => {
                    validate::validate_life_arc_transitions(l, &mut collector);
                },
                | Declaration::Schedule(s) => {
                    validate::validate_schedule_overlaps(s, &mut collector);
                },
                | Declaration::Behavior(_b) => {
                    // Behavior validation requires action registry, skip for
                    // now TODO: Add action registry support
                },
                | Declaration::Relationship(_r) => {
                    // Relationship bond validation happens at a different level
                    // (requires multiple relationships), skip for now
                },
                | _ => {},
            }
        }

        // Extract errors from collector
        // Since ErrorCollector doesn't expose errors directly, we use into_result and
        // extract
        match collector.into_result(()) {
            | Ok(_) => Vec::new(),
            | Err(ResolveError::MultipleErrors { errors, .. }) => errors,
            | Err(e) => vec![e],
        }
    }

    /// Lex text up to a position and return tokens with their spans
    /// Returns Vec<(start, token, end)>
    fn lex_until(text: &str, until: usize) -> Vec<(usize, crate::syntax::lexer::Token, usize)> {
        let lexer = Lexer::new(&text[..until.min(text.len())]);
        lexer.collect()
    }

    /// Parse the source text
    fn parse(text: &str) -> (Option<File>, Vec<ParseError>) {
        let lexer = Lexer::new(text);
        let parser = FileParser::new();

        match parser.parse(lexer) {
            | Ok(file) => (Some(file), Vec::new()),
            | Err(e) => {
                use lalrpop_util::ParseError as LalrpopError;

                let (start, end, message) = match e {
                    | LalrpopError::InvalidToken { location } => {
                        (location, location + 1, "Invalid token".to_string())
                    },
                    | LalrpopError::UnrecognizedEof { location, expected } => {
                        let expected_str = if expected.is_empty() {
                            String::new()
                        } else if expected.len() == 1 {
                            format!(" (expected {})", expected[0])
                        } else {
                            format!(" (expected one of: {})", expected.join(", "))
                        };
                        (
                            location,
                            location,
                            format!("Unexpected end of file{}", expected_str),
                        )
                    },
                    | LalrpopError::UnrecognizedToken {
                        token: (start, tok, end),
                        expected,
                    } => {
                        use crate::syntax::lexer::Token;

                        // Lex tokens up to the error position
                        let tokens = Self::lex_until(text, start);

                        // Check what we expected vs what we got
                        let expecting_close_brace = expected.iter().any(|e| e.contains("}"));
                        let expecting_comma = expected.iter().any(|e| e.contains(","));
                        let expecting_colon = expected.iter().any(|e| e.contains(":"));

                        let is_declaration_keyword = matches!(
                            tok,
                            Token::Character |
                                Token::Template |
                                Token::LifeArc |
                                Token::Schedule |
                                Token::Behavior |
                                Token::Institution |
                                Token::Relationship |
                                Token::Location |
                                Token::Species
                        );
                        let is_identifier = matches!(tok, Token::Ident(_));

                        // Case 1: Missing closing brace before new declaration
                        if expecting_close_brace && is_declaration_keyword {
                            // Find the last non-comment identifier token and the declaration
                            // keyword
                            let mut last_ident_name = None;
                            let mut last_ident_span = None;
                            let mut decl_keyword = None;

                            for (tok_start, token, tok_end) in tokens.iter().rev() {
                                match token {
                                    | Token::Ident(name) if last_ident_name.is_none() => {
                                        last_ident_name = Some(name.clone());
                                        last_ident_span = Some((*tok_start, *tok_end));
                                    },
                                    | Token::Character |
                                    Token::Template |
                                    Token::LifeArc |
                                    Token::Schedule |
                                    Token::Behavior |
                                    Token::Institution |
                                    Token::Relationship |
                                    Token::Location |
                                    Token::Species => {
                                        decl_keyword = Some(token);
                                        break;
                                    },
                                    | _ => {},
                                }
                            }

                            if let (Some((ident_start, ident_end)), Some(keyword)) =
                                (last_ident_span, decl_keyword)
                            {
                                let decl_type = match keyword {
                                    | Token::Character => "character",
                                    | Token::Template => "template",
                                    | Token::LifeArc => "life_arc",
                                    | Token::Schedule => "schedule",
                                    | Token::Behavior => "behavior",
                                    | Token::Institution => "institution",
                                    | Token::Relationship => "relationship",
                                    | Token::Location => "location",
                                    | Token::Species => "species",
                                    | _ => "declaration",
                                };
                                let decl_name =
                                    last_ident_name.unwrap_or_else(|| "unknown".to_string());
                                let message = format!(
                                    "Missing closing brace '}}' for {} {}",
                                    decl_type, decl_name
                                );
                                (ident_start, ident_end, message)
                            } else {
                                (start, end, "Missing closing brace '}'".to_string())
                            }
                        }
                        // Case 2: Missing comma or colon before identifier
                        else if (expecting_comma || expecting_colon) && is_identifier {
                            // Find the last identifier token before the error
                            if let Some((tok_start, Token::Ident(name), tok_end)) = tokens
                                .iter()
                                .rev()
                                .find(|(_, t, _)| matches!(t, Token::Ident(_)))
                            {
                                let message = if expecting_comma {
                                    format!("Missing comma after '{}'", name)
                                } else {
                                    format!("Missing ':' after '{}'", name)
                                };
                                (*tok_start, *tok_end, message)
                            } else {
                                let message = if expecting_comma {
                                    "Missing comma".to_string()
                                } else {
                                    "Missing ':'".to_string()
                                };
                                (start, end, message)
                            }
                        }
                        // Case 3: Other errors
                        else {
                            let message = if expected.len() == 1 {
                                let expected_token = &expected[0];
                                if expected_token.contains(";") {
                                    "Missing semicolon".to_string()
                                } else if expected_token.contains("}") {
                                    "Missing closing brace '}'".to_string()
                                } else {
                                    format!("Expected {}, found {:?}", expected_token, tok)
                                }
                            } else if !expected.is_empty() {
                                format!("Expected one of: {}, found {:?}", expected.join(", "), tok)
                            } else {
                                format!("Unexpected token {:?}", tok)
                            };
                            (start, end, message)
                        }
                    },
                    | LalrpopError::ExtraToken {
                        token: (start, tok, end),
                    } => (start, end, format!("Extra token {:?}", tok)),
                    | LalrpopError::User { error } => {
                        // Our custom ParseError already has span information
                        // Extract it if it's UnexpectedToken or other variants
                        use crate::syntax::ParseError as CustomParseError;
                        match error {
                            | CustomParseError::UnexpectedToken { token, span } => (
                                span.offset(),
                                span.offset() + span.len(),
                                format!("Unexpected token: {}", token),
                            ),
                            | CustomParseError::UnexpectedEof { span } => (
                                span.offset(),
                                span.offset() + span.len(),
                                "Unexpected end of file".to_string(),
                            ),
                            | CustomParseError::InvalidToken { span } => (
                                span.offset(),
                                span.offset() + span.len(),
                                "Invalid token".to_string(),
                            ),
                            | CustomParseError::UnclosedProseBlock { tag, span } => (
                                span.offset(),
                                span.offset() + span.len(),
                                format!("Unclosed prose block: ---{}", tag),
                            ),
                        }
                    },
                };

                let error = ParseError {
                    start,
                    end,
                    message,
                    severity: ErrorSeverity::Error,
                };
                (None, vec![error])
            },
        }
    }

    /// Get the word at a byte offset
    pub fn word_at_offset(&self, offset: usize) -> Option<String> {
        if offset > self.text.len() {
            return None;
        }

        let chars: Vec<char> = self.text.chars().collect();
        if chars.is_empty() {
            return None;
        }

        let mut byte_offset = 0;
        let mut char_index = 0;

        // Find the character index for this byte offset
        for (i, ch) in chars.iter().enumerate() {
            if byte_offset == offset {
                char_index = i;
                break;
            }
            byte_offset += ch.len_utf8();
            if i == chars.len() - 1 {
                // Last character
                char_index = i;
            }
        }

        // Check if we're on a word character
        if char_index >= chars.len() || !is_word_char(chars[char_index]) {
            return None;
        }

        // Find word boundaries
        let mut start = char_index;
        while start > 0 && is_word_char(chars[start - 1]) {
            start -= 1;
        }

        let mut end = char_index + 1;
        while end < chars.len() && is_word_char(chars[end]) {
            end += 1;
        }

        Some(chars[start..end].iter().collect())
    }
}

fn is_word_char(c: char) -> bool {
    c.is_alphanumeric() || c == '_'
}
