use std::sync::Arc;

use crate::{
    combine::{
        expected::Expected,
        parser::{Node, Parser},
        result::{PResultExt, ParseResult},
    },
    lexer::{
        Lexer,
        span::Spanned,
        token::{Keyword, Token, TokenKind, TokenTag},
    },
};
use rail_diag::NoteLevel;

pub struct Match<T, Kws: Keyword> {
    matcher: Arc<dyn Fn(Token<Kws>) -> Option<T>>,
    expected: Expected<Kws>,
    marker: std::marker::PhantomData<(T, Kws)>,
}

impl<T, Kws: Keyword> Clone for Match<T, Kws> {
    fn clone(&self) -> Self {
        Self {
            matcher: self.matcher.clone(),
            expected: self.expected.clone(),
            marker: std::marker::PhantomData,
        }
    }
}

impl<T: Node, Kws: Keyword> Match<T, Kws> {
    pub fn new(
        matcher: impl Fn(Token<Kws>) -> Option<T> + 'static,
        expected: impl IntoIterator<Item = TokenTag<Kws>>,
    ) -> Self {
        Self {
            matcher: Arc::new(matcher),
            expected: T::name().map(Expected::Name).unwrap_or_else(|| {
                Expected::Sequence(expected.into_iter().map(Expected::Token).collect())
            }),
            marker: std::marker::PhantomData,
        }
    }

    fn expects_ident(&self) -> bool {
        // Identify "expecting ident" based on the Expected payload.
        // This works for matcher!(Ident(_)) because it builds Expected::Sequence([Expected::Token(TokenTag::Ident)]).
        fn contains_ident<K: Keyword>(e: &Expected<K>) -> bool {
            match e {
                Expected::Token(TokenTag::Ident) => true,
                Expected::Sequence(items) | Expected::Alternatives(items) => {
                    items.iter().any(contains_ident)
                }
                Expected::Optional(inner) => contains_ident(inner),
                Expected::Defered(inner) => contains_ident(inner.get()),
                Expected::Name(_) => false,
                Expected::Token(_) => false,
            }
        }

        contains_ident(&self.expected)
    }
}

#[macro_export]
macro_rules! token {
    (Keyword($kws:ident :: $kind:ident $(($content:pat))?)) => {
        $crate::combine::impls::matcher::Match::new(
            |tok| match tok.kind() {
                $crate::lexer::token::TokenKind::Keyword($kws::$kind $(($content))?) => Some(tok),
                _ => None,
            },
            [$crate::lexer::token::TokenTag::Keyword(<<$kws as $crate::lexer::token::Keyword>::Tag>::$kind)]
        )
    };

    ($kind:ident $(($content:pat))?) => {
        $crate::combine::impls::matcher::Match::new(
            |tok| match tok.kind() {
                $crate::lexer::token::TokenKind::$kind $(($content))? => Some(tok),
                _ => None,
            },
            [$crate::lexer::token::TokenTag::$kind]
        )
    };

    ($( $kind:ident $( ($($inner:tt)*) )? => $body:expr ),* $(,)?) => {
        $crate::combine::impls::matcher::Match::new(
            |tok| match tok.kind().clone() {
                $(
                    $crate::token!(@pat $kind $( ( $($inner)* ) )? ) => {Some($body)}
                ,)*
                _ => None,
            },
            [$(
                $crate::token!(@tag $kind $( ( $($inner)* ) )? )
            ),*]
        )
    };

    (@tag Keyword($kws:ident :: $kind:ident $(($content:pat))?)) => {
        $crate::lexer::token::TokenTag::Keyword(<<$kws as Keyword>::Tag>::$kind)
    };

    (@tag $kind:ident $( ( $content:pat ) )?) => {
        $crate::lexer::token::TokenTag::$kind
    };

    (@pat Keyword($kws:ident :: $kind:ident $( ( $content:pat ) )? )) => {
        $crate::lexer::token::TokenKind::Keyword($kws::$kind $( ( $content ) )? )
    };

    (@pat $kind:ident $( ( $content:pat ) )?) => {
        $crate::lexer::token::TokenKind::$kind $( ( $content ) )?
    };
}

impl<Kw: Keyword> Parser<Kw> for TokenKind<Kw> {
    type Item = Self;

    fn parse<'lex, M: Fn(&mut Lexer<'lex, Kw>) -> bool>(
        &self,
        lexer: &mut Lexer<'lex, Kw>,
        _: M,
    ) -> ParseResult<Self::Item> {
        let peek = lexer.peek();
        let peek_tag = peek.kind().tag();
        let self_tag = self.tag();

        if peek_tag == self_tag {
            let tok = lexer.bump();
            let span = tok.span();
            ParseResult::succeeded(Spanned::new(tok.into_kind(), span))
        } else {
            let msg = format!("expected {}, found {}", self_tag, peek_tag);
            let diag = rail_diag::report!()
                .message(msg)
                .annotate(peek.span(), |builder| {
                    builder.message("unexpected token");
                })
                .build();
            ParseResult::desynced(diag)
        }
    }

    fn can_parse(&self, peek: &Token<Kw>) -> bool {
        self.tag() == peek.kind().tag()
    }

    fn expected(&self) -> &Expected<Kw> {
        // We leak here because:
        // 1. We can't store Expected in Token (can't modify the data structure)
        // 2. We need to return a 'static reference for the trait method
        // 3. Expected is small and the number of unique token tags is bounded
        // 4. This is only called during error reporting, not in the hot path
        Box::leak(Box::new(Expected::Token(self.tag())))
    }

    fn is_trivially_skippable() -> bool {
        true
    }
}

impl<Kws: Keyword, T: Node> Parser<Kws> for Match<T, Kws> {
    type Item = T;

    fn parse<'lex, M: Fn(&mut Lexer<'lex, Kws>) -> bool>(
        &self,
        lexer: &mut Lexer<'lex, Kws>,
        _: M,
    ) -> ParseResult<Self::Item> {
        let peek = lexer.peek();
        let kind = peek.kind().clone();
        let span = peek.span();

        match (self.matcher)(peek) {
            Some(item) => {
                let tok = lexer.bump();
                ParseResult::succeeded(Spanned::new(item, tok.span()))
            }
            None => {
                let msg = self.expected.format(kind.tag(), true);

                // Recovery rule:
                // If we expected an identifier but found a keyword, treat it as a *synced*
                // (recoverable) error instead of a hard desync.
                let found_is_keyword = matches!(kind.tag(), TokenTag::Keyword(_));
                if self.expects_ident() && found_is_keyword {
                    lexer.bump();
                    let tok = lexer.spanning(span);
                    let diag = rail_diag::report!()
                        .message(msg)
                        .annotate(span, |builder| {
                            builder.message(format!(
                                "`{}` is a reserved keyword and cannot be used as an identifier",
                                tok
                            )).level(NoteLevel::Warn);
                        })
                        .build();
                    ParseResult::synced(diag)
                } else {
                    let diag = rail_diag::report!()
                        .message(msg)
                        .annotate(span, |builder| {
                            builder.message("unexpected token");
                        })
                        .build();
                    ParseResult::desynced(diag)
                }
            }
        }
    }

    fn can_parse<'lex>(&self, peek: &Token<Kws>) -> bool {
        // Usual behavior: true if matcher accepts it.
        if (self.matcher)(peek.clone()).is_some() {
            return true;
        }

        // Recovery behavior: ident matcher should consider keywords "parsable"
        // so higher-level choice parsers can select this branch and get a Synced error.
        let found_is_keyword = matches!(peek.kind().tag(), TokenTag::Keyword(_));
        self.expects_ident() && found_is_keyword
    }

    fn is_trivially_skippable() -> bool {
        true
    }

    fn expected(&self) -> &Expected<Kws> {
        &self.expected
    }
}
