use std::marker::PhantomData;

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

#[derive(Debug, Clone)]
pub struct Optional<Kw: Keyword, T: Parser<Kw>> {
    inner: T,
    marker: PhantomData<Kw>,
    expected: Expected<Kw>,
}

impl<Kw: Keyword, T: Parser<Kw>> Optional<Kw, T> {
    pub fn new(inner: T) -> Self {
        let expected = Expected::optional(inner.expected().clone());
        Self {
            inner,
            marker: PhantomData,
            expected,
        }
    }
}

impl<T> Node for Option<T> {}

#[macro_export]
macro_rules! opt {
    ($inner:expr) => {
        $crate::combine::impls::option::Optional::new($inner.clone())
    };

    ($first:expr, $($rest:expr),+ ) => {
        $crate::combine::impls::option::Optional::new(seq!($first, $($rest),+))
    };

    ($inner:expr, |$pat:pat_param| $body:expr) => {
        $crate::map!($macro::opt!($inner), |$pat| $body)
    };

    ($first:expr, $($rest:expr),+ ; |$pat:pat_param| $body:expr) => {
        $crate::map!($macro::opt!($first, $($rest),+), |$pat| $body)
    };
}

impl<Kw: Keyword, T: Parser<Kw>> Parser<Kw> for Optional<Kw, T> {
    type Item = Option<T::Item>;

    fn parse<'lex, M: Fn(&mut Lexer<'lex, Kw>) -> bool>(
        &self,
        lexer: &mut Lexer<'lex, Kw>,
        should_stop: M,
    ) -> ParseResult<Self::Item> {
        let peek = lexer.peek();
        if self.can_parse(&peek) {
            self.inner.parse(lexer, should_stop).map_item(Some)
        } else {
            let span = peek.span();
            let span = lexer.last_char_span_of(span);
            ParseResult::succeeded(Spanned::new(None, span))
        }
    }

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

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

    fn is_optional() -> bool {
        true
    }

    fn is_trivially_skippable() -> bool {
        T::is_trivially_skippable()
    }
}
