use std::ops::Range;

use derive_more::{Display, Error};
use itertools::Itertools;

use crate::{
    ast::Stmt,
    lexer::{Lexer, LexerError, LexerErrorKind, SpannedToken, Token},
};

#[macro_use]
extern crate tracing;

pub mod ast;
pub mod lexer;

#[derive(Debug, Display, Error)]
pub enum ParserErrorKind {
    #[display("lexer error: {}", _0)]
    LexerError(LexerErrorKind),
    #[display("unexpected token: {}", _0)]
    UnexpectedToken(#[error(not(source))] String),
    #[display("duplicate primary key")]
    DuplicatePrimaryKey,
    #[display("missing primary key")]
    MissingPrimaryKey,
    #[display("negative tuple index")]
    NegativeTupleIndex,
    #[display("tuple index too large")]
    TupleIndexTooLarge,
    #[display("feature not supported yet: {}", _0)]
    UnsupportedFeature(#[error(not(source))] &'static str),
}

impl ParserErrorKind {
    pub(crate) fn unexpected_token(expected_list: &[Token], got: &Token) -> Self {
        debug_assert_ne!(expected_list.len(), 0, "supply a list of expected tokens");
        if expected_list.len() == 1 {
            Self::UnexpectedToken(format!(
                "expected {}, but got {}",
                expected_list[0].name(),
                got.name()
            ))
        } else {
            Self::UnexpectedToken(format!(
                "expected one of: {}, but got {}",
                expected_list.iter().map(|t| t.name()).join(", "),
                got.name()
            ))
        }
    }
}

#[derive(Debug, Display, Error)]
#[display("parser error at {:?}: {}", span, kind)]
pub struct ParseError {
    pub span: Range<usize>,
    pub kind: ParserErrorKind,
}

// flatten the lexer error's span into the parser error
impl From<LexerError> for ParseError {
    fn from(value: LexerError) -> Self {
        Self {
            span: value.span,
            kind: ParserErrorKind::LexerError(value.kind),
        }
    }
}

pub(crate) struct Parser<'a> {
    lexer: Lexer<'a>,
    statements: Vec<Stmt<'a>>,
    errors: Vec<ParseError>,
    current_span: Range<usize>,
}

impl<'a> Parser<'a> {
    /// Tries to consume one of the token types provided in `expected_list` and extends the current
    /// span window to contain that token. If the next token is not withing `expected_list`,
    /// returns a [`ParserError`] of kind [`ParserErrorKind::UnexpectedToken`].
    #[instrument(skip(self), level = "trace")]
    pub(crate) fn consume(
        &mut self,
        expected_list: &[Token],
    ) -> Result<&SpannedToken<'a>, ParseError> {
        let tok = self.lexer.next();
        if expected_list.contains(&tok.token) {
            trace!(got = ?tok.token, "consumed token");
            self.current_span.end = tok.span.end;
            Ok(tok)
        } else {
            trace!(got = ?tok.token, "could not consume");
            Err(ParseError {
                span: tok.span.clone(),
                kind: ParserErrorKind::unexpected_token(expected_list, &tok.token),
            })
        }
    }

    /// Sync up to the next parsing restart point on errors.
    #[instrument(skip_all, level = "trace")]
    fn sync(&mut self) {
        loop {
            match &self.lexer.peek().token {
                // consume, nobody upstream wants this
                Token::Semicolon => {
                    trace!("synced to after semicolon");
                    self.lexer.advance();
                    break;
                }

                // break but do not consume, parent context owns these
                token @ (Token::RBrace
                | Token::RBracket
                | Token::RParen
                | Token::Comma
                | Token::Create
                | Token::Select
                | Token::Insert
                | Token::Eof) => {
                    trace!("synced to {:?}", token);
                    break;
                }

                // skip everything else
                _ => self.lexer.advance(),
            }
        }
    }

    fn parse_all(mut self) -> (Vec<Stmt<'a>>, Vec<ParseError>) {
        while !self.lexer.reached_eof() {
            match self.parse_stmt() {
                Ok(stmt) => self.statements.push(stmt),
                Err(err) => {
                    self.errors.push(err);
                    self.sync();
                }
            }
        }

        (self.statements, self.errors)
    }

    #[cfg(test)]
    pub(crate) fn assert_finished(mut self) {
        assert!(
            self.errors.is_empty(),
            "expected no parser errors, got: {:?}",
            self.errors
        );
        assert!(
            self.lexer.reached_eof(),
            "expected EOF, but next token is {:?}",
            self.lexer.peek().token
        );
    }

    fn new(source: &'a str) -> Self {
        let (lexer, lexer_errors) = Lexer::lex(source);
        Parser {
            lexer,
            current_span: 0..0,
            statements: Vec::new(),
            errors: lexer_errors.into_iter().map(Into::into).collect(),
        }
    }
}

#[instrument(skip_all, level = "debug")]
pub fn parse<'a>(source: &'a str) -> (Vec<Stmt<'a>>, Vec<ParseError>) {
    Parser::new(source).parse_all()
}

#[cfg(test)]
mod tests {
    use crate::{Parser, ParserErrorKind, ast::*, lexer::Token, parse};

    pub(crate) fn assert_no_errors(errors: &[crate::ParseError]) {
        if !errors.is_empty() {
            for err in errors {
                eprintln!("{}", err);
            }
            panic!("expected no errors, got {}", errors.len());
        }
    }

    #[test]
    fn consume_wrong_token_returns_unexpected_token() {
        let mut parser = Parser::new("table");
        let err = parser.consume(&[Token::Create]).unwrap_err();
        assert!(matches!(err.kind, ParserErrorKind::UnexpectedToken(_)));
    }

    #[test]
    fn test_create_database_works_after_error_sync() {
        // finds IDENTIFIER ("garbage"), then syncs up to `;`
        let source = "garbage code; create database main;";

        let (statements, errors) = parse(source);
        assert!(matches!(
            errors[0].kind,
            ParserErrorKind::UnexpectedToken(_),
        ));
        assert_eq!(errors.len(), 1);

        let Stmt::CreateDatabase(CreateDatabaseStmt { name, .. }) = &statements[0] else {
            panic!("invalid statement type: {:?}", &statements[0]);
        };
        assert_eq!(name.name, "main".into());
    }
}
