//! Parse error types.

use sipha_core::{span::Span, traits::RuleId, traits::TokenKind};

use crate::expected::Expected;

/// Type alias for error context stack.
type ErrorContextVec<R> = Vec<ErrorContext<R>>;

/// Context information for error reporting, tracking where an error occurred.
#[derive(Clone, Debug)]
pub struct ErrorContext<R: RuleId> {
    /// Rule ID where the error occurred.
    pub rule_id: R,
    /// Span where the error occurred.
    pub span: Span,
    /// Contextual message describing what was being parsed.
    pub message: String,
}

/// Parse error emitted by the grammar and Pratt parser.
#[derive(Clone, Debug)]
pub enum ParseError<K: TokenKind, R: RuleId = ()> {
    /// Unexpected token encountered.
    UnexpectedToken {
        kind: K,
        span: Span,
        rule_context: Option<R>,
        #[allow(clippy::type_complexity)]
        context_stack: ErrorContextVec<R>,
    },
    /// Specific token kind was expected.
    Expected {
        expected: Expected<K>,
        found: Option<K>,
        span: Span,
        rule_context: Option<R>,
        #[allow(clippy::type_complexity)]
        context_stack: ErrorContextVec<R>,
    },
    /// Tried to look up a Pratt rule that does not exist.
    NoRule {
        kind: K,
        span: Span,
        rule_context: Option<R>,
        #[allow(clippy::type_complexity)]
        context_stack: ErrorContextVec<R>,
    },
    /// Operator declared as non-associative but used twice in a row.
    NonAssociative {
        kind: K,
        span: Span,
        rule_context: Option<R>,
        #[allow(clippy::type_complexity)]
        context_stack: ErrorContextVec<R>,
    },
    /// Conditional rule evaluated to false.
    InvalidContext {
        rule: &'static str,
        span: Span,
        rule_context: Option<R>,
        #[allow(clippy::type_complexity)]
        context_stack: ErrorContextVec<R>,
    },
    /// Ran out of tokens while parsing.
    UnexpectedEof {
        span: Span,
        rule_context: Option<R>,
        #[allow(clippy::type_complexity)]
        context_stack: ErrorContextVec<R>,
    },
    /// Repetition exceeded its declared bounds.
    RangeError {
        min: usize,
        max: usize,
        found: usize,
        span: Span,
        rule_context: Option<R>,
        #[allow(clippy::type_complexity)]
        context_stack: ErrorContextVec<R>,
    },
    /// Multiple alternatives failed in a choice rule.
    MultipleAlternatives {
        #[allow(clippy::type_complexity)]
        alternatives: Vec<ParseError<K, R>>,
        span: Span,
        rule_context: Option<R>,
        #[allow(clippy::type_complexity)]
        context_stack: ErrorContextVec<R>,
    },
}

impl<K: TokenKind, R: RuleId> ParseError<K, R> {
    /// Convert the parse error into a lightweight diagnostic.
    ///
    /// Requires the `diagnostics` feature to be enabled.
    #[cfg(feature = "diagnostics")]
    #[must_use]
    pub fn to_diagnostic(&self) -> crate::diagnostic::Diagnostic {
        use crate::diagnostic::{Diagnostic, Level};

        match self {
            ParseError::UnexpectedToken {
                kind,
                span,
                rule_context,
                context_stack,
            } => {
                let message =
                    Self::add_rule_context(format!("Unexpected token {:?}", kind), *rule_context);
                let hints = vec![
                    "Check if you're missing a token before this position".to_string(),
                    "Verify the syntax matches the expected grammar".to_string(),
                ];

                Diagnostic {
                    level: Level::Error,
                    message,
                    spans: vec![span.clone()],
                    notes: Self::build_notes_from_context(context_stack),
                    hints,
                    suggestions: vec![],
                }
            }
            ParseError::Expected {
                expected,
                found,
                span,
                rule_context,
                context_stack,
            } => {
                let expected_str = match expected {
                    Expected::Single(k) => format!("{:?}", k),
                    Expected::Set(ks) => ks
                        .iter()
                        .map(|k| format!("{:?}", k))
                        .collect::<Vec<_>>()
                        .join(", "),
                    Expected::Dynamic(v) => v
                        .iter()
                        .map(|k| format!("{:?}", k))
                        .collect::<Vec<_>>()
                        .join(", "),
                    Expected::Multiple(alternatives) => {
                        let mut parts = Vec::new();
                        for alt in alternatives {
                            match alt {
                                Expected::Single(k) => parts.push(format!("{:?}", k)),
                                Expected::Set(ks) => {
                                    parts.extend(ks.iter().map(|k| format!("{:?}", k)));
                                }
                                Expected::Dynamic(v) => {
                                    parts.extend(v.iter().map(|k| format!("{:?}", k)));
                                }
                                Expected::Multiple(_) => {
                                    parts.push("...".to_string());
                                }
                            }
                        }
                        parts.join(", ")
                    }
                };
                let message = Self::add_rule_context(
                    format!("Expected {}, found {:?}", expected_str, found),
                    *rule_context,
                );
                let mut suggestions = Vec::new();

                if let Some(found_token) = found {
                    if let Expected::Single(expected_token) = expected {
                        let found_str = format!("{:?}", found_token);
                        let expected_str = format!("{:?}", expected_token);

                        if Self::simple_similarity(&found_str, &expected_str) > 0.7 {
                            suggestions.push(format!("Did you mean `{}`?", expected_str));
                        }
                    } else if let Expected::Set(expected_tokens) = expected {
                        let found_str = format!("{:?}", found_token);
                        let mut best_match: Option<String> = None;
                        let mut best_score = 0.0;

                        for expected_token in expected_tokens.iter() {
                            let expected_str = format!("{:?}", expected_token);
                            let score = Self::simple_similarity(&found_str, &expected_str);
                            if score > best_score && score > 0.7 {
                                best_score = score;
                                best_match = Some(expected_str);
                            }
                        }

                        if let Some(matched) = best_match {
                            suggestions.push(format!("Did you mean `{}`?", matched));
                        }
                    }
                }

                Diagnostic {
                    level: Level::Error,
                    message,
                    spans: vec![span.clone()],
                    notes: Self::build_notes_from_context(context_stack),
                    hints: vec![],
                    suggestions,
                }
            }
            ParseError::MultipleAlternatives {
                alternatives,
                span,
                rule_context,
                context_stack,
            } => {
                let mut messages = Vec::new();
                let mut all_expected = Vec::new();

                for err in alternatives {
                    #[cfg(feature = "diagnostics")]
                    {
                        let diag = err.to_diagnostic();
                        messages.push(diag.message);
                    }
                    if let ParseError::Expected { expected, .. } = err {
                        match expected {
                            Expected::Single(k) => all_expected.push(format!("{:?}", k)),
                            Expected::Set(ks) => {
                                all_expected.extend(ks.iter().map(|k| format!("{:?}", k)));
                            }
                            Expected::Dynamic(v) => {
                                all_expected.extend(v.iter().map(|k| format!("{:?}", k)));
                            }
                            Expected::Multiple(_) => {}
                        }
                    }
                }

                let expected_str = if all_expected.is_empty() {
                    "one of the alternatives".to_string()
                } else {
                    format!("one of: {}", all_expected.join(", "))
                };

                let message = Self::add_rule_context(
                    format!("Expected {}, but all alternatives failed", expected_str),
                    *rule_context,
                );

                let mut all_notes = messages;
                all_notes.extend(context_stack.iter().rev().map(|ctx| ctx.message.clone()));

                Diagnostic {
                    level: Level::Error,
                    message,
                    spans: vec![span.clone()],
                    notes: all_notes,
                    hints: vec![],
                    suggestions: vec![],
                }
            }
            ParseError::NoRule {
                kind,
                span,
                rule_context,
                context_stack,
            } => {
                let message = Self::add_rule_context(
                    format!("No Pratt rule registered for {:?}", kind),
                    *rule_context,
                );
                Diagnostic {
                    level: Level::Error,
                    message,
                    spans: vec![span.clone()],
                    notes: Self::build_notes_from_context(context_stack),
                    hints: vec![],
                    suggestions: vec![],
                }
            }
            ParseError::NonAssociative {
                kind,
                span,
                rule_context,
                context_stack,
            } => {
                let message = Self::add_rule_context(
                    format!("Operator {:?} is non-associative", kind),
                    *rule_context,
                );
                Diagnostic {
                    level: Level::Error,
                    message,
                    spans: vec![span.clone()],
                    notes: Self::build_notes_from_context(context_stack),
                    hints: vec!["Non-associative operators cannot be chained".to_string()],
                    suggestions: vec!["Add parentheses to clarify precedence".to_string()],
                }
            }
            ParseError::InvalidContext {
                rule,
                span,
                rule_context,
                context_stack,
            } => {
                let message = Self::add_rule_context(
                    format!("Rule {} is disabled in this context", rule),
                    *rule_context,
                );
                Diagnostic {
                    level: Level::Error,
                    message,
                    spans: vec![span.clone()],
                    notes: Self::build_notes_from_context(context_stack),
                    hints: vec![],
                    suggestions: vec![],
                }
            }
            ParseError::UnexpectedEof {
                span,
                rule_context,
                context_stack,
            } => {
                let message =
                    Self::add_rule_context("Unexpected end of input".to_string(), *rule_context);
                let hints = vec![
                    "The input ended before the parser could complete".to_string(),
                    "Check if you're missing closing brackets, parentheses, or other delimiters"
                        .to_string(),
                ];

                let suggestions = vec!["Add the missing tokens to complete the parse".to_string()];

                Diagnostic {
                    level: Level::Error,
                    message,
                    spans: vec![span.clone()],
                    notes: Self::build_notes_from_context(context_stack),
                    hints,
                    suggestions,
                }
            }
            ParseError::RangeError {
                min,
                max,
                found,
                span,
                rule_context,
                context_stack,
            } => {
                let message = Self::add_rule_context(
                    format!(
                        "Expected between {} and {} repetitions, found {}",
                        min, max, found
                    ),
                    *rule_context,
                );
                Diagnostic {
                    level: Level::Error,
                    message,
                    spans: vec![span.clone()],
                    notes: Self::build_notes_from_context(context_stack),
                    hints: vec![],
                    suggestions: vec![],
                }
            }
        }
    }

    /// Build notes from a context stack.
    #[allow(dead_code)]
    fn build_notes_from_context(context_stack: &[ErrorContext<R>]) -> Vec<String> {
        context_stack
            .iter()
            .rev()
            .map(|ctx| ctx.message.clone())
            .collect()
    }

    /// Add rule context to a message if a rule is present.
    #[allow(dead_code)]
    fn add_rule_context(message: String, rule_context: Option<R>) -> String {
        if let Some(rule) = rule_context {
            format!("While parsing {:?}, {}", rule, message)
        } else {
            message
        }
    }

    /// Simple similarity metric between two strings (Levenshtein distance normalized).
    /// Returns a value between 0.0 and 1.0, where 1.0 is identical.
    #[cfg(feature = "diagnostics")]
    fn simple_similarity(s1: &str, s2: &str) -> f64 {
        if s1 == s2 {
            return 1.0;
        }
        if s1.is_empty() || s2.is_empty() {
            return 0.0;
        }

        let s1_lower = s1.to_lowercase();
        let s2_lower = s2.to_lowercase();

        if s1_lower == s2_lower {
            return 0.95;
        }

        if s1_lower.starts_with(&s2_lower) || s2_lower.starts_with(&s1_lower) {
            return 0.8;
        }

        if s1_lower.contains(&s2_lower) || s2_lower.contains(&s1_lower) {
            return 0.7;
        }

        let distance = Self::levenshtein_distance(&s1_lower, &s2_lower);
        let max_len = s1.len().max(s2.len());
        if max_len == 0 {
            return 0.0;
        }
        1.0 - (distance as f64 / max_len as f64)
    }

    /// Calculate Levenshtein distance between two strings.
    #[cfg(feature = "diagnostics")]
    fn levenshtein_distance(s1: &str, s2: &str) -> usize {
        let s1_chars: Vec<char> = s1.chars().collect();
        let s2_chars: Vec<char> = s2.chars().collect();
        let s1_len = s1_chars.len();
        let s2_len = s2_chars.len();

        if s1_len == 0 {
            return s2_len;
        }
        if s2_len == 0 {
            return s1_len;
        }

        let mut matrix = vec![vec![0; s2_len + 1]; s1_len + 1];

        for (i, row) in matrix.iter_mut().enumerate().take(s1_len + 1) {
            row[0] = i;
        }
        for (j, val) in matrix[0].iter_mut().enumerate().take(s2_len + 1) {
            *val = j;
        }

        for i in 1..=s1_len {
            for j in 1..=s2_len {
                let cost = if s1_chars[i - 1] == s2_chars[j - 1] {
                    0
                } else {
                    1
                };
                matrix[i][j] = (matrix[i - 1][j] + 1)
                    .min(matrix[i][j - 1] + 1)
                    .min(matrix[i - 1][j - 1] + cost);
            }
        }

        matrix[s1_len][s2_len]
    }
}
