use std::marker::PhantomData;

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

// Wrapper type for separated items with trailing separator flag
// Implements Node so it can be used as Parser::Item
#[derive(Clone, Debug)]
pub struct SeparatedItems<T: Node>(pub Vec<Spanned<T>>, pub bool);

impl<T: Node> Node for SeparatedItems<T> {
    fn name() -> Option<&'static str> {
        None
    }
}

// Parses `item (sep item)*` with optional trailing separator.
// Returns SeparatedItems(items, has_trailing_sep) where has_trailing_sep indicates if a trailing separator was parsed.
// Zero items is always valid — returns an empty Vec with has_trailing_sep = false.
pub struct Separated<K: Keyword, I: Parser<K>, S: Parser<K>> {
    item: I,
    sep: S,
    expected: Expected<K>,
}

impl<K: Keyword, I: Parser<K>, S: Parser<K>> Clone for Separated<K, I, S> {
    fn clone(&self) -> Self {
        Self {
            item: self.item.clone(),
            sep: self.sep.clone(),
            expected: self.expected.clone(),
        }
    }
}

fn join_diags(acc: &mut Option<Diagnostics>, new: Diagnostics) {
    match acc {
        Some(curr) => curr.join(new),
        None => {
            *acc = Some(new);
        }
    }
}

impl<K: Keyword, I: Parser<K>, S: Parser<K>> Separated<K, I, S> {
    // `item (sep item)* sep?`
    pub fn new(item: I, sep: S) -> Self {
        let expected = item.expected().clone();
        Self {
            item,
            sep,
            expected,
        }
    }
}

impl<K: Keyword, I: Parser<K>, S: Parser<K>> Parser<K> for Separated<K, I, S> {
    type Item = SeparatedItems<I::Item>;

    fn parse<'lex, M: Fn(&mut Lexer<'lex, K>) -> bool>(
        &self,
        lexer: &mut Lexer<'lex, K>,
        should_stop: M,
    ) -> ParseResult<Self::Item> {
        let item = &self.item;
        let sep = &self.sep;

        let mut items: Vec<Spanned<I::Item>> = Vec::new();
        let mut has_trailing_sep = false;
        let mut diags: Option<Diagnostics> = None;

        enum Stage {
            Item,
            Separator,
        }

        let mut stage = Stage::Item;

        loop {
            match stage {
                Stage::Item => {
                    if !item.can_parse(&lexer.peek()) {
                        break;
                    }

                    match item.parse(lexer, |lex: &mut Lexer<'lex, K>| {
                        sep.can_parse(&lex.peek()) || should_stop(lex)
                    }) {
                        Ok(State::Item(i)) => {
                            items.push(i);
                        }
                        Ok(State::Synced(d)) => {
                            join_diags(&mut diags, d);
                        }
                        Err(d) => {
                            // Hard fail: once we've committed to the list, the first item must succeed
                            return ParseResult::desynced(d);
                        }
                    }

                    stage = Stage::Separator;
                }
                Stage::Separator => {
                    if !sep.can_parse(&lexer.peek()) {
                        break;
                    }

                    match sep.parse(lexer, |lex: &mut Lexer<'lex, K>| {
                        item.can_parse(&lex.peek()) || should_stop(lex)
                    }) {
                        Ok(State::Item(_)) => {
                            // Check if there's another item after this separator
                            if item.can_parse(&lexer.peek()) {
                                has_trailing_sep = false;
                                stage = Stage::Item;
                            } else {
                                // Trailing separator (no item after it)
                                has_trailing_sep = true;
                                break;
                            }
                        }
                        Ok(State::Synced(d)) => {
                            join_diags(&mut diags, d);
                            // Check if there's another item after this separator
                            if item.can_parse(&lexer.peek()) {
                                has_trailing_sep = false;
                                stage = Stage::Item;
                            } else {
                                // Trailing separator (no item after it)
                                has_trailing_sep = true;
                                break;
                            }
                        }
                        Err(d) => {
                            // Hard fail: once we've committed to the list, the first item must succeed
                            return ParseResult::desynced(d);
                        }
                    }
                }
            }
        }

        let span = match items.first() {
            Some(first) => items.iter().fold(first.span, |acc, it| acc.union(&it.span)),
            None => {
                let peek_span = lexer.peek().span();
                lexer.last_char_span_of(peek_span)
            }
        };

        match diags {
            Some(d) => Ok(State::Synced(d)),
            None => {
                ParseResult::succeeded(Spanned::new(SeparatedItems(items, has_trailing_sep), span))
            }
        }
    }

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

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

    fn is_optional() -> bool {
        true
    }

    fn is_trivially_skippable() -> bool {
        false
    }
}

#[macro_export]
macro_rules! sep {
    // No map
    ($item:expr, $sep:expr) => {
        $crate::combine::impls::separated::Separated::new($item.clone(), $sep.clone())
    };

    // With map
    ($item:expr, $sep:expr; |$pat:pat_param| $body:expr) => {
        $crate::map!(
            $crate::combine::impls::separated::Separated::new($item.clone(), $sep.clone()),
            |$pat| $body
        )
    };
}
