use std::{marker::PhantomData, sync::Arc};

use crate::{
    combine::{
        parser::{Node, Parser},
        result::PResultExt,
    },
    lexer::token::Keyword,
};

pub struct Map<Kw: Keyword, U: Node, P: Parser<Kw>> {
    expected: crate::combine::expected::Expected<Kw>,
    map: Arc<dyn Fn(P::Item) -> U>,
    inner: P,
    marker: PhantomData<Kw>,
}

#[macro_export]
macro_rules! map {
    ($expr:expr, |$pat:pat_param| $body:expr) => {
        $crate::combine::impls::map::Map::new($expr.clone(), |$pat| $body)
    };
}

impl<Kw: Keyword, U: Node, P: Parser<Kw>> Clone for Map<Kw, U, P> {
    fn clone(&self) -> Self {
        Self {
            expected: self.expected.clone(),
            map: self.map.clone(),
            inner: self.inner.clone(),
            marker: self.marker,
        }
    }
}

impl<Kw: Keyword, U: Node, P: Parser<Kw>> Map<Kw, U, P> {
    pub fn new(inner: P, map: impl Fn(P::Item) -> U + 'static) -> Self {
        Self {
            expected: U::name()
                .map(crate::combine::expected::Expected::Name)
                .unwrap_or_else(|| inner.expected().clone()),
            map: Arc::new(map),
            inner,
            marker: PhantomData,
        }
    }
}

impl<Kw: Keyword, U: Node, P: Parser<Kw>> Parser<Kw> for Map<Kw, U, P> {
    type Item = U;

    fn parse<'lex, M: Fn(&mut crate::lexer::Lexer<'lex, Kw>) -> bool>(
        &self,
        lexer: &mut crate::lexer::Lexer<'lex, Kw>,
        should_stop: M,
    ) -> crate::combine::result::ParseResult<Self::Item> {
        self.inner
            .parse(lexer, should_stop)
            .map_item(|item| (self.map)(item))
    }

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

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

    fn is_optional() -> bool {
        P::is_optional()
    }

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