use crate::internal::as2::lexer::tokens::{QuoteKind, Token, TokenKind};
use crate::internal::span::Spanned;
use std::borrow::Cow;
use winnow::combinator::{alt, repeat};
use winnow::error::{ContextError, ErrMode, ParseError};
use winnow::stream::TokenSlice;
use winnow::token::literal;
use winnow::{ModalResult, Parser};

mod class;
pub(crate) mod document;
mod expression;
mod operator;
mod statement;
#[cfg(test)]
mod tests;

pub(crate) type Tokens<'i> = TokenSlice<'i, Token<'i>>;

use crate::internal::as2::ast::Document;

pub fn parse_document<'i>(
    source: &'i [Token<'i>],
) -> winnow::Result<Document<'i>, ParseError<Tokens<'i>, ContextError>> {
    document::document.parse(TokenSlice::new(source))
}

fn string<'i>(i: &mut Tokens<'i>) -> ModalResult<Spanned<Cow<'i, str>>> {
    skip_newlines(i)?;
    let (raw, span) = alt((
        TokenKind::String(QuoteKind::Double).map(|t| (t.raw, t.span)),
        TokenKind::String(QuoteKind::Single).map(|t| (t.raw, t.span)),
    ))
    .parse_next(i)?;
    if !raw.contains("\\") {
        return Ok(Spanned::new(span, Cow::Borrowed(raw)));
    }
    let mut out = String::with_capacity(raw.len());
    let mut iter = raw.chars();
    while let Some(ch) = iter.next() {
        if ch == '\\' {
            match iter.next() {
                Some('n') => out.push('\n'),
                Some('r') => out.push('\r'),
                Some('t') => out.push('\t'),
                Some('\\') => out.push('\\'),
                Some('"') => out.push('"'),
                Some('\'') => out.push('\''),
                Some(other) => {
                    out.push('\\');
                    out.push(other);
                }
                None => {
                    out.push('\\');
                }
            }
        } else {
            out.push(ch);
        }
    }
    Ok(Spanned::new(span, Cow::Owned(out)))
}

fn identifier<'i>(i: &mut Tokens<'i>) -> ModalResult<Spanned<&'i str>> {
    skip_newlines(i)?;
    let token = TokenKind::Identifier.parse_next(i)?;
    Ok(Spanned::new(token.span, token.raw))
}

pub(crate) fn ignore_newlines<'i: 'i, O, P>(
    mut inner: P,
) -> impl Parser<Tokens<'i>, O, ErrMode<ContextError>>
where
    P: Parser<Tokens<'i>, O, ErrMode<ContextError>>,
{
    move |input: &mut Tokens<'i>| {
        skip_newlines(input)?;
        inner.parse_next(input)
    }
}

pub(crate) fn skip_newlines(i: &mut Tokens<'_>) -> ModalResult<()> {
    repeat(0.., literal(TokenKind::Newline))
        .map(|()| ())
        .parse_next(i)
}
