//! Error types and display formatting for parse errors.
//!
//! This module provides structured error types for the lexer and parser,
//! with rich diagnostic output including:
//!
//! - Source location with line/column numbers
//! - Syntax-highlighted error messages
//! - Socratic explanations for common mistakes
//! - Spelling suggestions for unknown words
//!
//! # Error Display
//!
//! Errors can be displayed with source context using [`ParseError::display_with_source`],
//! which produces rustc-style error output with underlined spans.

use logicaffeine_base::Interner;
use crate::style::Style;
use crate::suggest::{find_similar, KNOWN_WORDS};
use crate::token::{Span, TokenType};

/// A parse error with location information.
#[derive(Debug, Clone)]
pub struct ParseError {
    pub kind: ParseErrorKind,
    pub span: Span,
}

impl ParseError {
    pub fn display_with_source(&self, source: &str) -> String {
        let (line_num, line_start, line_content) = self.find_context(source);
        let col = self.span.start.saturating_sub(line_start);
        let len = (self.span.end - self.span.start).max(1);
        let underline = format!("{}{}", " ".repeat(col), "^".repeat(len));

        let error_label = Style::bold_red("error");
        let kind_str = format!("{:?}", self.kind);
        let line_num_str = Style::blue(&format!("{:4}", line_num));
        let pipe = Style::blue("|");
        let underline_colored = Style::red(&underline);

        let mut result = format!(
            "{}: {}\n\n{} {} {}\n     {} {}",
            error_label, kind_str, line_num_str, pipe, line_content, pipe, underline_colored
        );

        if let Some(word) = self.extract_word(source) {
            if let Some(suggestion) = find_similar(&word, KNOWN_WORDS, 2) {
                let hint = Style::cyan("help");
                result.push_str(&format!("\n     {} {}: did you mean '{}'?", pipe, hint, Style::green(suggestion)));
            }
        }

        result
    }

    fn extract_word<'a>(&self, source: &'a str) -> Option<&'a str> {
        if self.span.start < source.len() && self.span.end <= source.len() {
            let word = &source[self.span.start..self.span.end];
            if !word.is_empty() && word.chars().all(|c| c.is_alphabetic()) {
                return Some(word);
            }
        }
        None
    }

    fn find_context<'a>(&self, source: &'a str) -> (usize, usize, &'a str) {
        let mut line_num = 1;
        let mut line_start = 0;

        for (i, c) in source.char_indices() {
            if i >= self.span.start {
                break;
            }
            if c == '\n' {
                line_num += 1;
                line_start = i + 1;
            }
        }

        let line_end = source[line_start..]
            .find('\n')
            .map(|off| line_start + off)
            .unwrap_or(source.len());

        (line_num, line_start, &source[line_start..line_end])
    }
}

#[derive(Debug, Clone)]
pub enum ParseErrorKind {
    UnexpectedToken {
        expected: TokenType,
        found: TokenType,
    },
    ExpectedContentWord {
        found: TokenType,
    },
    ExpectedCopula,
    UnknownQuantifier {
        found: TokenType,
    },
    UnknownModal {
        found: TokenType,
    },
    ExpectedVerb {
        found: TokenType,
    },
    ExpectedTemporalAdverb,
    ExpectedPresuppositionTrigger,
    ExpectedFocusParticle,
    ExpectedScopalAdverb,
    ExpectedSuperlativeAdjective,
    ExpectedComparativeAdjective,
    ExpectedThan,
    ExpectedNumber,
    EmptyRestriction,
    GappingResolutionFailed,
    StativeProgressiveConflict,
    UndefinedVariable {
        name: String,
    },
    UseAfterMove {
        name: String,
    },
    IsValueEquality {
        variable: String,
        value: String,
    },
    ZeroIndex,
    ExpectedStatement,
    ExpectedKeyword { keyword: String },
    ExpectedExpression,
    ExpectedIdentifier,
    /// Subject and object lists have different lengths in a "respectively" construction.
    RespectivelyLengthMismatch {
        subject_count: usize,
        object_count: usize,
    },
    /// Type mismatch during static type checking.
    TypeMismatch {
        expected: String,
        found: String,
    },
    /// Type mismatch with context (e.g., "in argument 2 of 'compute'").
    TypeMismatchDetailed {
        expected: String,
        found: String,
        context: String,
    },
    /// A type variable would occur in its own definition (e.g., `T = List<T>`).
    InfiniteType {
        var_description: String,
        type_description: String,
    },
    /// Wrong number of arguments in a function call.
    ArityMismatch {
        function: String,
        expected: usize,
        found: usize,
    },
    /// A field name does not exist on the struct type.
    FieldNotFound {
        type_name: String,
        field_name: String,
        available: Vec<String>,
    },
    /// Tried to call something that is not a function.
    NotAFunction {
        found_type: String,
    },
    /// Invalid refinement predicate in a dependent type.
    InvalidRefinementPredicate,
    /// Grammar error (e.g., "its" vs "it's").
    GrammarError(String),
    /// DRS scope violation (pronoun trapped in negation, disjunction, etc.).
    ScopeViolation(String),
    /// Unresolved pronoun in discourse mode - no accessible antecedent found.
    UnresolvedPronoun {
        gender: crate::drs::Gender,
        number: crate::drs::Number,
    },
    /// Custom error message (used for escape analysis, zone errors, etc.).
    Custom(String),
}

#[cold]
pub fn socratic_explanation(error: &ParseError, _interner: &Interner) -> String {
    let pos = error.span.start;
    match &error.kind {
        ParseErrorKind::UnexpectedToken { expected, found } => {
            format!(
                "I was following your logic, but I stumbled at position {}. \
                I expected {:?}, but found {:?}. Perhaps you meant to use a different word here?",
                pos, expected, found
            )
        }
        ParseErrorKind::ExpectedContentWord { found } => {
            format!(
                "I was looking for a noun, verb, or adjective at position {}, \
                but found {:?} instead. The logic needs a content word to ground it.",
                pos, found
            )
        }
        ParseErrorKind::ExpectedCopula => {
            format!(
                "At position {}, I expected 'is' or 'are' to link the subject and predicate. \
                Without it, the sentence structure is incomplete.",
                pos
            )
        }
        ParseErrorKind::UnknownQuantifier { found } => {
            format!(
                "At position {}, I found {:?} where I expected a quantifier like 'all', 'some', or 'no'. \
                These words tell me how many things we're talking about.",
                pos, found
            )
        }
        ParseErrorKind::UnknownModal { found } => {
            format!(
                "At position {}, I found {:?} where I expected a modal like 'must', 'can', or 'should'. \
                Modals express possibility, necessity, or obligation.",
                pos, found
            )
        }
        ParseErrorKind::ExpectedVerb { found } => {
            format!(
                "At position {}, I expected a verb to describe an action or state, \
                but found {:?}. Every sentence needs a verb.",
                pos, found
            )
        }
        ParseErrorKind::ExpectedTemporalAdverb => {
            format!(
                "At position {}, I expected a temporal adverb like 'yesterday' or 'tomorrow' \
                to anchor the sentence in time.",
                pos
            )
        }
        ParseErrorKind::ExpectedPresuppositionTrigger => {
            format!(
                "At position {}, I expected a presupposition trigger like 'stopped', 'realized', or 'regrets'. \
                These words carry hidden assumptions.",
                pos
            )
        }
        ParseErrorKind::ExpectedFocusParticle => {
            format!(
                "At position {}, I expected a focus particle like 'only', 'even', or 'just'. \
                These words highlight what's important in the sentence.",
                pos
            )
        }
        ParseErrorKind::ExpectedScopalAdverb => {
            format!(
                "At position {}, I expected a scopal adverb that modifies the entire proposition.",
                pos
            )
        }
        ParseErrorKind::ExpectedSuperlativeAdjective => {
            format!(
                "At position {}, I expected a superlative adjective like 'tallest' or 'fastest'. \
                These words compare one thing to all others.",
                pos
            )
        }
        ParseErrorKind::ExpectedComparativeAdjective => {
            format!(
                "At position {}, I expected a comparative adjective like 'taller' or 'faster'. \
                These words compare two things.",
                pos
            )
        }
        ParseErrorKind::ExpectedThan => {
            format!(
                "At position {}, I expected 'than' after the comparative. \
                Comparisons need 'than' to introduce the thing being compared to.",
                pos
            )
        }
        ParseErrorKind::ExpectedNumber => {
            format!(
                "At position {}, I expected a numeric value like '2', '3.14', or 'aleph_0'. \
                Measure phrases require a number.",
                pos
            )
        }
        ParseErrorKind::EmptyRestriction => {
            format!(
                "At position {}, the restriction clause is empty. \
                A relative clause needs content to restrict the noun phrase.",
                pos
            )
        }
        ParseErrorKind::GappingResolutionFailed => {
            format!(
                "At position {}, I see a gapped construction (like '...and Mary, a pear'), \
                but I couldn't find a verb in the previous clause to borrow. \
                Gapping requires a clear action to repeat.",
                pos
            )
        }
        ParseErrorKind::StativeProgressiveConflict => {
            format!(
                "At position {}, a stative verb like 'know' or 'love' cannot be used with progressive aspect. \
                Stative verbs describe states, not activities in progress.",
                pos
            )
        }
        ParseErrorKind::UndefinedVariable { name } => {
            format!(
                "At position {}, I found '{}' but this variable has not been defined. \
                In imperative mode, all variables must be declared before use.",
                pos, name
            )
        }
        ParseErrorKind::UseAfterMove { name } => {
            format!(
                "At position {}, I found '{}' but this value has been moved. \
                Once a value is moved, it cannot be used again.",
                pos, name
            )
        }
        ParseErrorKind::IsValueEquality { variable, value } => {
            format!(
                "At position {}, I found '{} is {}' but 'is' is for type/predicate checks. \
                For value equality, use '{} equals {}'.",
                pos, variable, value, variable, value
            )
        }
        ParseErrorKind::ZeroIndex => {
            format!(
                "At position {}, I found 'item 0' but indices in LOGOS start at 1. \
                In English, 'the 1st item' is the first item, not the zeroth. \
                Try 'item 1 of list' to get the first element.",
                pos
            )
        }
        ParseErrorKind::ExpectedStatement => {
            format!(
                "At position {}, I expected a statement like 'Let', 'Set', or 'Return'.",
                pos
            )
        }
        ParseErrorKind::ExpectedKeyword { keyword } => {
            format!(
                "At position {}, I expected the keyword '{}'.",
                pos, keyword
            )
        }
        ParseErrorKind::ExpectedExpression => {
            format!(
                "At position {}, I expected an expression (number, variable, or computation).",
                pos
            )
        }
        ParseErrorKind::ExpectedIdentifier => {
            format!(
                "At position {}, I expected an identifier (variable name).",
                pos
            )
        }
        ParseErrorKind::RespectivelyLengthMismatch { subject_count, object_count } => {
            format!(
                "At position {}, 'respectively' requires equal-length lists. \
                The subject has {} element(s) and the object has {} element(s). \
                Each subject must pair with exactly one object.",
                pos, subject_count, object_count
            )
        }
        ParseErrorKind::TypeMismatch { expected, found } => {
            format!(
                "At position {}, I expected a value of type '{}' but found '{}'. \
                Types must match in LOGOS. Check that your value matches the declared type.",
                pos, expected, found
            )
        }
        ParseErrorKind::TypeMismatchDetailed { expected, found, context } => {
            let ctx_note = if context.is_empty() {
                String::new()
            } else {
                format!(" ({})", context)
            };
            format!(
                "At position {}, I expected '{}' but found '{}'{}.  \
                Check that the types are consistent — perhaps the annotation or the value needs adjusting.",
                pos, expected, found, ctx_note
            )
        }
        ParseErrorKind::InfiniteType { var_description, type_description } => {
            format!(
                "At position {}, I detected an infinite recursive type: {} would need to equal {}. \
                A type cannot contain itself. Consider using a named record or a level of indirection.",
                pos, var_description, type_description
            )
        }
        ParseErrorKind::ArityMismatch { function, expected, found } => {
            format!(
                "At position {}, '{}' takes {} argument(s) but was called with {}. \
                Check the function signature and ensure you pass the right number of values.",
                pos, function, expected, found
            )
        }
        ParseErrorKind::FieldNotFound { type_name, field_name, available } => {
            let avail_note = if available.is_empty() {
                String::new()
            } else {
                format!(" Available fields: {}.", available.join(", "))
            };
            format!(
                "At position {}, '{}' has no field named '{}'.{} \
                Check the spelling or use one of the declared fields.",
                pos, type_name, field_name, avail_note
            )
        }
        ParseErrorKind::NotAFunction { found_type } => {
            format!(
                "At position {}, I tried to call a value of type '{}' as a function. \
                Only functions and closures can be called. \
                Check that you are applying arguments to an actual function.",
                pos, found_type
            )
        }
        ParseErrorKind::InvalidRefinementPredicate => {
            format!(
                "At position {}, the refinement predicate is not valid. \
                A refinement predicate must be a comparison like 'x > 0' or 'n < 100'.",
                pos
            )
        }
        ParseErrorKind::GrammarError(msg) => {
            format!(
                "At position {}, grammar issue: {}",
                pos, msg
            )
        }
        ParseErrorKind::ScopeViolation(msg) => {
            format!(
                "At position {}, scope violation: {}. The pronoun cannot access a referent \
                trapped in a different scope (e.g., inside negation or disjunction).",
                pos, msg
            )
        }
        ParseErrorKind::UnresolvedPronoun { gender, number } => {
            format!(
                "At position {}, I found a {:?} {:?} pronoun but couldn't resolve it. \
                In discourse mode, all pronouns must have an accessible antecedent from earlier sentences. \
                The referent may be trapped in an inaccessible scope (negation, disjunction) or \
                there may be no matching referent.",
                pos, gender, number
            )
        }
        ParseErrorKind::Custom(msg) => msg.clone(),
    }
}

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

    #[test]
    fn parse_error_has_span() {
        let error = ParseError {
            kind: ParseErrorKind::ExpectedCopula,
            span: Span::new(5, 10),
        };
        assert_eq!(error.span.start, 5);
        assert_eq!(error.span.end, 10);
    }

    #[test]
    fn display_with_source_shows_line_and_underline() {
        let error = ParseError {
            kind: ParseErrorKind::ExpectedCopula,
            span: Span::new(8, 14),
        };
        let source = "All men mortal are.";
        let display = error.display_with_source(source);
        assert!(display.contains("mortal"), "Should contain source word: {}", display);
        assert!(display.contains("^^^^^^"), "Should contain underline: {}", display);
    }

    #[test]
    fn display_with_source_suggests_typo_fix() {
        let error = ParseError {
            kind: ParseErrorKind::ExpectedCopula,
            span: Span::new(0, 5),
        };
        let source = "logoc is the study of reason.";
        let display = error.display_with_source(source);
        assert!(display.contains("did you mean"), "Should suggest fix: {}", display);
        assert!(display.contains("logic"), "Should suggest 'logic': {}", display);
    }

    #[test]
    fn display_with_source_has_color_codes() {
        let error = ParseError {
            kind: ParseErrorKind::ExpectedCopula,
            span: Span::new(0, 3),
        };
        let source = "Alll men are mortal.";
        let display = error.display_with_source(source);
        assert!(display.contains("\x1b["), "Should contain ANSI escape codes: {}", display);
    }

    // =========================================================================
    // Phase 4 — Type error reporting variants
    // =========================================================================

    #[test]
    fn type_mismatch_detailed_socratic_mentions_types() {
        let interner = logicaffeine_base::Interner::new();
        let error = ParseError {
            kind: ParseErrorKind::TypeMismatchDetailed {
                expected: "Int".to_string(),
                found: "Bool".to_string(),
                context: "in let binding".to_string(),
            },
            span: Span::new(0, 0),
        };
        let explanation = socratic_explanation(&error, &interner);
        assert!(explanation.contains("Int"), "Should mention expected type: {}", explanation);
        assert!(explanation.contains("Bool"), "Should mention found type: {}", explanation);
        assert!(explanation.contains("let binding"), "Should include context: {}", explanation);
    }

    #[test]
    fn type_mismatch_detailed_without_context_is_clean() {
        let interner = logicaffeine_base::Interner::new();
        let error = ParseError {
            kind: ParseErrorKind::TypeMismatchDetailed {
                expected: "Text".to_string(),
                found: "Int".to_string(),
                context: String::new(),
            },
            span: Span::new(0, 0),
        };
        let explanation = socratic_explanation(&error, &interner);
        assert!(explanation.contains("Text"), "Should mention expected type: {}", explanation);
        assert!(explanation.contains("Int"), "Should mention found type: {}", explanation);
        // No spurious "()" from empty context
        assert!(!explanation.contains("()"), "Empty context should not leave '()': {}", explanation);
    }

    #[test]
    fn infinite_type_socratic_mentions_both_descriptions() {
        let interner = logicaffeine_base::Interner::new();
        let error = ParseError {
            kind: ParseErrorKind::InfiniteType {
                var_description: "type variable α0".to_string(),
                type_description: "Seq of α0".to_string(),
            },
            span: Span::new(0, 0),
        };
        let explanation = socratic_explanation(&error, &interner);
        assert!(explanation.contains("α0"), "Should mention var: {}", explanation);
        assert!(explanation.contains("Seq of α0"), "Should mention type: {}", explanation);
    }

    #[test]
    fn arity_mismatch_socratic_mentions_function_and_counts() {
        let interner = logicaffeine_base::Interner::new();
        let error = ParseError {
            kind: ParseErrorKind::ArityMismatch {
                function: "double".to_string(),
                expected: 1,
                found: 3,
            },
            span: Span::new(0, 0),
        };
        let explanation = socratic_explanation(&error, &interner);
        assert!(explanation.contains("double"), "Should name the function: {}", explanation);
        assert!(explanation.contains("1"), "Should mention expected count: {}", explanation);
        assert!(explanation.contains("3"), "Should mention found count: {}", explanation);
    }

    #[test]
    fn field_not_found_socratic_mentions_type_and_field() {
        let interner = logicaffeine_base::Interner::new();
        let error = ParseError {
            kind: ParseErrorKind::FieldNotFound {
                type_name: "Point".to_string(),
                field_name: "z".to_string(),
                available: vec!["x".to_string(), "y".to_string()],
            },
            span: Span::new(0, 0),
        };
        let explanation = socratic_explanation(&error, &interner);
        assert!(explanation.contains("Point"), "Should name the type: {}", explanation);
        assert!(explanation.contains("z"), "Should name the missing field: {}", explanation);
        assert!(explanation.contains("x"), "Should list available fields: {}", explanation);
        assert!(explanation.contains("y"), "Should list available fields: {}", explanation);
    }

    #[test]
    fn not_a_function_socratic_mentions_found_type() {
        let interner = logicaffeine_base::Interner::new();
        let error = ParseError {
            kind: ParseErrorKind::NotAFunction {
                found_type: "Int".to_string(),
            },
            span: Span::new(0, 0),
        };
        let explanation = socratic_explanation(&error, &interner);
        assert!(explanation.contains("Int"), "Should mention the type found: {}", explanation);
        assert!(explanation.to_lowercase().contains("function"), "Should mention function: {}", explanation);
    }
}
