//! Best-effort error recovery utilities.
//!
//! The parser recovers from errors by skipping tokens until a synchronization
//! token is found. The sync target is chosen from [`SyncLevel`] based on
//! the error kind. Methods on [`ParseResult`] let callers widen the error span,
//! synchronize, report, and produce fallback values.
//!
//! General idea is that any method that returns something other than an error
//! can be assumed to have reported it.

use crate::ast::{CallableKind, IdentifierToken, QualifiedIdentifier};
use crate::diagnostics::diagnostic_from_parse_error;
use crate::lexer::{Token, TokenPosition, TokenSpan};
use crate::parser::{ParseError, ParseResult, Parser};

/// Synchronization groups the parser can stop at during recovery.
///
/// The variant order defines recovery strength: later variants are treated as
/// "stronger" boundaries, so synchronizing to a weaker level will also stop
/// at any stronger one.
///
/// This enum is intentionally coarse-grained and semantic. It is not meant to
/// encode arbitrary token sets.
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub enum SyncLevel {
    /// Tokens that can reasonably continue or restart an expression.
    ///
    /// This is the loosest recovery level.
    Expression,

    /// Separator between homogeneous list elements, e.g. `,`.
    ///
    /// Synchronizing here also stops at closing delimiters and stronger
    /// structural boundaries.
    ListSeparator,

    /// Closing `>` of an angle-bracket-delimited type/class argument list.
    CloseAngleBracket,

    /// Closing `)` of a parenthesized/grouped construct.
    CloseParenthesis,

    /// Closing `]` of an index or bracket-delimited construct.
    CloseBracket,

    /// A statement boundary or statement starter.
    ///
    /// Includes `;` and keywords that begin standalone statements /
    /// statement-like control-flow forms.
    Statement,

    /// Start of a `switch` arm.
    ///
    /// This is useful because `case` / `default` are stronger boundaries than
    /// ordinary statements inside switch parsing.
    SwitchArmStart,

    /// Start of a declaration-like item.
    ///
    /// Used for recovery in declaration-containing bodies where the next
    /// sensible point is "the next member/declaration" rather than merely
    /// "some statement".
    DeclarationStart,

    /// A hard block boundary.
    ///
    /// This is the strongest normal recovery point.
    BlockBoundary,
}

impl SyncLevel {
    /// Converts a token to its synchronization class, if any.
    const fn for_token(token: Token) -> Option<Self> {
        use crate::lexer::Keyword;
        use SyncLevel::{
            BlockBoundary, CloseAngleBracket, CloseBracket, CloseParenthesis, DeclarationStart,
            Expression, ListSeparator, Statement, SwitchArmStart,
        };

        match token {
            // Expression-level recovery points
            Token::Exponentiation
            | Token::Increment
            | Token::Decrement
            | Token::Not
            | Token::BitwiseNot
            | Token::Multiply
            | Token::Divide
            | Token::Modulo
            | Token::Plus
            | Token::Minus
            | Token::ConcatSpace
            | Token::Concat
            | Token::LeftShift
            | Token::LogicalRightShift
            | Token::RightShift
            | Token::LessEqual
            | Token::GreaterEqual
            | Token::Equal
            | Token::NotEqual
            | Token::ApproximatelyEqual
            | Token::BitwiseAnd
            | Token::BitwiseOr
            | Token::BitwiseXor
            | Token::LogicalAnd
            | Token::LogicalXor
            | Token::LogicalOr
            | Token::Assign
            | Token::MultiplyAssign
            | Token::DivideAssign
            | Token::ModuloAssign
            | Token::PlusAssign
            | Token::MinusAssign
            | Token::ConcatAssign
            | Token::ConcatSpaceAssign
            | Token::Period
            | Token::Question
            | Token::Colon
            | Token::LeftParenthesis
            | Token::Identifier
            | Token::Keyword(Keyword::Dot | Keyword::Cross | Keyword::ClockwiseFrom) => {
                Some(Expression)
            }

            // List / delimiter boundaries
            Token::Comma => Some(ListSeparator),
            Token::Greater => Some(CloseAngleBracket),
            Token::RightParenthesis => Some(CloseParenthesis),
            Token::RightBracket => Some(CloseBracket),

            // Statement-level boundaries
            Token::Semicolon
            | Token::Keyword(
                Keyword::If
                | Keyword::Else
                | Keyword::Switch
                | Keyword::For
                | Keyword::ForEach
                | Keyword::While
                | Keyword::Do
                | Keyword::Until
                | Keyword::Return
                | Keyword::Break
                | Keyword::Continue
                | Keyword::Local,
            ) => Some(Statement),

            // Switch-specific stronger boundary
            Token::Keyword(Keyword::Case | Keyword::Default) => Some(SwitchArmStart),

            // Declaration/member starts
            Token::Keyword(
                Keyword::Class
                | Keyword::Struct
                | Keyword::Enum
                | Keyword::State
                | Keyword::Function
                | Keyword::Event
                | Keyword::Delegate
                | Keyword::Operator
                | Keyword::Var
                | Keyword::Replication
                | Keyword::NativeReplication
                | Keyword::DefaultProperties
                | Keyword::CppText
                | Keyword::CppStruct,
            )
            | Token::ExecDirective => Some(DeclarationStart),

            // Hard structural stop
            Token::LeftBrace | Token::CppBlock | Token::RightBrace => Some(BlockBoundary),

            _ => None,
        }
    }
}

impl Parser<'_, '_> {
    /// Converts a parse error into a diagnostic and queues it.
    ///
    /// Placeholder implementation.
    pub fn report_error(&mut self, error: ParseError) {
        //self.diagnostics.push(Diagnostic::from(error));
        self.diagnostics
            .push(diagnostic_from_parse_error(error, self.file()));
    }

    /// Reports a parser error with [`crate::parser::ParseErrorKind`] at
    /// the current location and queues an appropriate diagnostic.
    pub fn report_error_here(&mut self, error_kind: crate::parser::ParseErrorKind) {
        let new_error = self.make_error_here(error_kind);
        self.report_error(new_error);
    }

    /// Skips tokens until a token with `min_sync` level or stronger is found.
    ///
    /// Reaches end-of-file if no qualifying token is found.
    pub(crate) fn recover_until(&mut self, min_sync: SyncLevel) {
        while let Some(next_token) = self.peek_token() {
            if let Some(next_token_sync_level) = SyncLevel::for_token(next_token)
                && next_token_sync_level >= min_sync
            {
                break;
            }
            self.advance();
        }
    }
}

/// Supplies a fallback value after a parse error so parsing can continue and
/// reveal further errors.
pub trait RecoveryFallback<'src, 'arena>: Sized {
    fn fallback_value(parser: &Parser<'src, 'arena>, err: &ParseError) -> Self;
}

/// Extends [`ParseResult`] with recovery-related methods for
/// fluent error handling.
/// !!!! Can we store a parser reference instead of passing it into every method?
pub trait ResultRecoveryExt<'src, 'arena, T>: Sized {
    /// Extends the left end of the error span to `from`.
    ///
    /// Does nothing if `Self` is `Ok(...)`.
    #[must_use]
    fn widen_error_span_from(self, from: TokenPosition) -> Self;

    fn blame(self, blame_span: TokenSpan) -> Self;
    fn related(self, tag: impl Into<String>, related_span: TokenSpan) -> Self;

    fn blame_token(self, blame_position: TokenPosition) -> Self {
        self.blame(TokenSpan::new(blame_position))
    }

    fn extend_blame_to_next_token(self, parser: &mut Parser<'src, 'arena>) -> Self;

    fn extend_blame_start_to_covered_start(self) -> Self;
    fn extend_blame_end_to_covered_end(self) -> Self;

    fn related_token(self, tag: impl Into<String>, related_position: TokenPosition) -> Self {
        self.related(tag, TokenSpan::new(related_position))
    }

    /// Extends the right end of the error span up to but not including
    /// the next token of the given sync `level`.
    ///
    /// Does nothing if `Self` is `Ok(...)`.
    #[must_use]
    fn sync_error_until(self, parser: &mut Parser<'src, 'arena>, level: SyncLevel) -> Self;

    /// Extends the right end of the error span to include the next token of
    /// the given sync `level`.
    ///
    /// Does nothing if `Self` is `Ok(...)`.
    #[must_use]
    fn sync_error_at(self, parser: &mut Parser<'src, 'arena>, level: SyncLevel) -> Self;

    /// Either returns expected value or its best effort fallback.
    #[must_use]
    fn unwrap_or_fallback(self, parser: &mut Parser<'src, 'arena>) -> T
    where
        T: RecoveryFallback<'src, 'arena>;

    /// Produces the contained value if successful,
    /// or a fallback if an error occurred.
    fn report_error(self, parser: &mut Parser<'src, 'arena>) -> bool;

    fn ok_or_report(self, parser: &mut Parser<'src, 'arena>) -> Option<T>;
}

impl<'src, 'arena, T> ResultRecoveryExt<'src, 'arena, T> for ParseResult<'src, 'arena, T> {
    fn widen_error_span_from(mut self, from: TokenPosition) -> Self {
        if let Err(ref mut error) = self {
            error.covered_span.start = std::cmp::min(error.covered_span.start, from);
        }
        self
    }

    fn blame(self, blame_span: TokenSpan) -> Self {
        self.map_err(|error| error.blame(blame_span))
    }

    fn extend_blame_to_next_token(self, parser: &mut Parser<'src, 'arena>) -> Self {
        self.map_err(|error| error.extend_blame_to_next_token(parser))
    }

    fn extend_blame_start_to_covered_start(self) -> Self {
        self.map_err(|error| error.extend_blame_start_to_covered_start())
    }

    fn extend_blame_end_to_covered_end(self) -> Self {
        self.map_err(|error| error.extend_blame_end_to_covered_end())
    }

    fn related(self, tag: impl Into<String>, related_span: TokenSpan) -> Self {
        self.map_err(|error| error.related(tag, related_span))
    }

    fn sync_error_until(mut self, parser: &mut Parser<'src, 'arena>, level: SyncLevel) -> Self {
        if let Err(ref mut error) = self {
            parser.recover_until(level);
            error.covered_span.end = std::cmp::max(
                error.covered_span.end,
                parser.last_consumed_position_or_start(),
            );
        }
        self
    }

    fn sync_error_at(mut self, parser: &mut Parser<'src, 'arena>, level: SyncLevel) -> Self {
        if let Err(ref mut error) = self {
            parser.recover_until(level);
            // If we're at end-of-file, this'll simply do nothing.
            if parser
                .peek_token()
                .and_then(SyncLevel::for_token)
                .is_some_and(|next_level| next_level == level)
            {
                parser.advance();
            }
            error.covered_span.end = std::cmp::max(
                error.covered_span.end,
                parser.last_consumed_position_or_start(),
            );
        }
        self
    }

    fn unwrap_or_fallback(self, parser: &mut Parser<'src, 'arena>) -> T
    where
        T: RecoveryFallback<'src, 'arena>,
    {
        self.unwrap_or_else(|error| {
            let value = T::fallback_value(parser, &error);
            parser.report_error(error);
            value
        })
    }

    fn report_error(self, parser: &mut Parser<'src, 'arena>) -> bool {
        if let Err(error) = self {
            parser.report_error(error);
            true
        } else {
            false
        }
    }

    fn ok_or_report(self, parser: &mut Parser<'src, 'arena>) -> Option<T> {
        match self {
            Ok(value) => Some(value),
            Err(error) => {
                parser.report_error(error);
                None
            }
        }
    }
}

impl<'src, 'arena> ResultRecoveryExt<'src, 'arena, ()> for ParseError {
    fn widen_error_span_from(mut self, from: TokenPosition) -> Self {
        self.covered_span.start = std::cmp::min(self.covered_span.start, from);
        self
    }

    fn blame(mut self, blame_span: TokenSpan) -> Self {
        self.blame_span = blame_span;
        self
    }

    fn extend_blame_to_next_token(mut self, parser: &mut Parser<'src, 'arena>) -> Self {
        self.blame_span.end = parser.peek_position_or_eof();
        self
    }

    fn extend_blame_start_to_covered_start(mut self) -> Self {
        self.blame_span.start = self.covered_span.start;
        self
    }

    fn extend_blame_end_to_covered_end(mut self) -> Self {
        self.blame_span.end = self.covered_span.end;
        self
    }

    fn related(mut self, tag: impl Into<String>, related_span: TokenSpan) -> Self {
        self.related_spans.insert(tag.into(), related_span);
        self
    }

    fn sync_error_until(mut self, parser: &mut Parser<'src, 'arena>, level: SyncLevel) -> Self {
        parser.recover_until(level);
        self.covered_span.end = std::cmp::max(
            self.covered_span.end,
            parser.last_consumed_position_or_start(),
        );
        self
    }

    fn sync_error_at(mut self, parser: &mut Parser<'src, 'arena>, level: SyncLevel) -> Self {
        parser.recover_until(level);

        if parser
            .peek_token()
            .and_then(SyncLevel::for_token)
            .is_some_and(|next_level| next_level == level)
        {
            parser.advance();
        }

        self.covered_span.end = std::cmp::max(
            self.covered_span.end,
            parser.last_consumed_position_or_start(),
        );
        self
    }

    fn unwrap_or_fallback(self, parser: &mut Parser<'src, 'arena>) {
        parser.report_error(self);
    }

    fn report_error(self, parser: &mut Parser<'src, 'arena>) -> bool {
        parser.report_error(self);
        true
    }

    fn ok_or_report(self, parser: &mut Parser<'src, 'arena>) -> Option<()> {
        parser.report_error(self);
        None
    }
}

impl<'src, 'arena> RecoveryFallback<'src, 'arena> for i128 {
    fn fallback_value(_: &Parser<'src, 'arena>, _: &ParseError) -> Self {
        Self::default()
    }
}

impl<'src, 'arena> RecoveryFallback<'src, 'arena> for f64 {
    fn fallback_value(_: &Parser<'src, 'arena>, _: &ParseError) -> Self {
        Self::default()
    }
}

impl<'src, 'arena> RecoveryFallback<'src, 'arena> for crate::ast::IdentifierToken {
    fn fallback_value(_: &Parser<'src, 'arena>, error: &ParseError) -> Self {
        Self(error.covered_span.start)
    }
}

impl<'src, 'arena> RecoveryFallback<'src, 'arena>
    for crate::ast::CallableDefinitionRef<'src, 'arena>
{
    fn fallback_value(parser: &Parser<'src, 'arena>, err: &ParseError) -> Self {
        // default return type: Named("") at error span
        let ret_id = crate::ast::IdentifierToken(err.covered_span.start);
        let return_type = crate::arena::ArenaNode::new_in(
            crate::ast::TypeSpecifier::Named(QualifiedIdentifier::from_ident(parser.arena, ret_id)),
            err.covered_span,
            parser.arena,
        );

        let def = crate::ast::CallableDefinition {
            name: crate::ast::CallableName::Identifier(IdentifierToken(err.covered_span.start)),
            kind: CallableKind::Function,
            return_type_specifier: Some(return_type),
            modifiers: parser.arena.vec(),
            parameters: parser.arena.vec(),
            body: None,
        };
        crate::arena::ArenaNode::new_in(def, err.covered_span, parser.arena)
    }
}

impl<'src, 'arena> RecoveryFallback<'src, 'arena> for crate::ast::StructDefRef<'src, 'arena> {
    fn fallback_value(parser: &Parser<'src, 'arena>, err: &ParseError) -> Self {
        let def = crate::ast::StructDefinition {
            name: None,
            base_type_name: None,
            modifiers: parser.arena.vec(),
            fields: parser.arena.vec(),
        };
        crate::arena::ArenaNode::new_in(def, err.covered_span, parser.arena)
    }
}

impl<'src, 'arena> RecoveryFallback<'src, 'arena> for crate::ast::ClassVarDeclRef<'src, 'arena> {
    fn fallback_value(parser: &Parser<'src, 'arena>, err: &ParseError) -> Self {
        let dummy_ident = crate::ast::IdentifierToken(err.covered_span.start);
        let type_spec = crate::arena::ArenaNode::new_in(
            crate::ast::TypeSpecifier::Named(QualifiedIdentifier::from_ident(
                parser.arena,
                dummy_ident,
            )),
            err.covered_span,
            parser.arena,
        );
        let def = crate::ast::ClassVarDecl {
            paren_specs: None,
            modifiers: parser.arena.vec(),
            type_spec,
            declarators: parser.arena.vec(),
            span: err.covered_span,
        };
        crate::arena::ArenaNode::new_in(def, err.covered_span, parser.arena)
    }
}

impl<'src, 'arena> RecoveryFallback<'src, 'arena>
    for crate::ast::ReplicationBlockRef<'src, 'arena>
{
    fn fallback_value(parser: &Parser<'src, 'arena>, err: &ParseError) -> Self {
        let def = crate::ast::ReplicationBlock {
            rules: parser.arena.vec(),
            span: err.covered_span,
        };
        crate::arena::ArenaNode::new_in(def, err.covered_span, parser.arena)
    }
}

impl<'src, 'arena> RecoveryFallback<'src, 'arena> for crate::ast::StateDeclRef<'src, 'arena> {
    fn fallback_value(parser: &Parser<'src, 'arena>, err: &ParseError) -> Self {
        let def = crate::ast::StateDecl {
            name: crate::ast::IdentifierToken(err.covered_span.start),
            parent: None,
            modifiers: parser.arena.vec(),
            ignores: None,
            body: parser.arena.vec(),
            span: err.covered_span,
        };
        crate::arena::ArenaNode::new_in(def, err.covered_span, parser.arena)
    }
}

impl<'src, 'arena> RecoveryFallback<'src, 'arena> for TokenPosition {
    fn fallback_value(_: &Parser<'src, 'arena>, error: &ParseError) -> Self {
        error.covered_span.end
    }
}

impl<'src, 'arena> RecoveryFallback<'src, 'arena> for (Token, TokenPosition) {
    fn fallback_value(_: &Parser<'src, 'arena>, error: &ParseError) -> Self {
        (Token::Error, error.covered_span.end)
    }
}

impl<'src, 'arena> RecoveryFallback<'src, 'arena> for crate::ast::ExpressionRef<'src, 'arena> {
    fn fallback_value(parser: &Parser<'src, 'arena>, error: &ParseError) -> Self {
        crate::arena::ArenaNode::new_in(
            crate::ast::Expression::Error,
            error.covered_span,
            parser.arena,
        )
    }
}

impl<'src, 'arena> RecoveryFallback<'src, 'arena> for crate::ast::StatementRef<'src, 'arena> {
    fn fallback_value(parser: &Parser<'src, 'arena>, error: &ParseError) -> Self {
        crate::arena::ArenaNode::new_in(
            crate::ast::Statement::Error,
            error.covered_span,
            parser.arena,
        )
    }
}

impl<'src, 'arena, T> RecoveryFallback<'src, 'arena> for Option<T> {
    fn fallback_value(_: &Parser<'src, 'arena>, _: &ParseError) -> Self {
        None
    }
}

impl<'src, 'arena> RecoveryFallback<'src, 'arena> for crate::ast::ClassConstDeclRef<'src, 'arena> {
    fn fallback_value(parser: &Parser<'src, 'arena>, err: &ParseError) -> Self {
        let name = crate::ast::IdentifierToken(err.covered_span.start);
        let value = crate::ast::DeclarationLiteralRef {
            literal: crate::ast::DeclarationLiteral::None,
            position: err.covered_span.start,
        };
        let def = crate::ast::ClassConstDecl {
            name,
            value,
            span: err.covered_span,
        };
        crate::arena::ArenaNode::new_in(def, err.covered_span, parser.arena)
    }
}

impl<'src, 'arena> RecoveryFallback<'src, 'arena> for crate::ast::TypeSpecifierRef<'src, 'arena> {
    fn fallback_value(parser: &Parser<'src, 'arena>, err: &ParseError) -> Self {
        let dummy = crate::ast::IdentifierToken(err.covered_span.start);
        crate::arena::ArenaNode::new_in(
            crate::ast::TypeSpecifier::Named(QualifiedIdentifier::from_ident(parser.arena, dummy)),
            err.covered_span,
            parser.arena,
        )
    }
}

impl<'src, 'arena> RecoveryFallback<'src, 'arena> for crate::ast::ExecDirectiveRef<'arena> {
    fn fallback_value(parser: &Parser<'src, 'arena>, err: &ParseError) -> Self {
        let def = crate::ast::ExecDirective {
            text: parser.arena.string(""),
            span: err.covered_span,
        };
        crate::arena::ArenaNode::new_in(def, err.covered_span, parser.arena)
    }
}
