use apollo_parser::{Parser as ApolloParser, SyntaxTree};
use rowan::GreenNode;
use crate::database::inputs::InputDatabase;
use crate::diagnostics::{ApolloDiagnostic, DiagnosticData, Label};
use crate::FileId;
#[salsa::query_group(AstStorage)]
pub trait AstDatabase: InputDatabase {
    #[salsa::invoke(ast)]
    fn ast(&self, file_id: FileId) -> SyntaxTree;
    #[salsa::invoke(document)]
    fn document(&self, file_id: FileId) -> GreenNode;
    #[salsa::invoke(syntax_errors)]
    fn syntax_errors(&self) -> Vec<ApolloDiagnostic>;
}
fn ast(db: &dyn AstDatabase, file_id: FileId) -> SyntaxTree {
    let input = db.input(file_id).text();
    let mut parser = ApolloParser::new(&input);
    if let Some(limit) = db.recursion_limit() {
        parser = parser.recursion_limit(limit)
    };
    if let Some(limit) = db.token_limit() {
        parser = parser.token_limit(limit)
    };
    parser.parse()
}
fn document(db: &dyn AstDatabase, file_id: FileId) -> GreenNode {
    db.ast(file_id).green()
}
fn syntax_errors(db: &dyn AstDatabase) -> Vec<ApolloDiagnostic> {
    db.source_files()
        .into_iter()
        .flat_map(|file_id| {
            db.ast(file_id)
                .errors()
                .map(|err| {
                    if err.is_limit() {
                        ApolloDiagnostic::new(
                            db,
                            (file_id, err.index(), err.data().len()).into(),
                            DiagnosticData::LimitExceeded {
                                message: err.message().into(),
                            },
                        )
                        .label(Label::new(
                            (file_id, err.index(), err.data().len()),
                            err.message(),
                        ))
                    } else {
                        ApolloDiagnostic::new(
                            db,
                            (file_id, err.index(), err.data().len()).into(),
                            DiagnosticData::SyntaxError {
                                message: err.message().into(),
                            },
                        )
                        .label(Label::new(
                            (file_id, err.index(), err.data().len()),
                            err.message(),
                        ))
                    }
                })
                .collect::<Vec<ApolloDiagnostic>>()
        })
        .collect()
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::{ApolloCompiler, HirDatabase};
    #[test]
    fn it_errors_when_selection_set_recursion_limit_exceeded() {
        let schema = r#"
        query {
          Q1 {
            url {
              hostname
            }
          }
        }
        "#;
        let mut compiler = ApolloCompiler::new().recursion_limit(1);
        let doc_id = compiler.add_document(schema, "schema.graphql");
        let ast = compiler.db.ast(doc_id);
        assert_eq!(ast.recursion_limit().high, 2);
        assert_eq!(ast.errors().len(), 1);
        assert_eq!(ast.document().definitions().count(), 1);
    }
    #[test]
    fn it_passes_when_selection_set_recursion_limit_is_not_exceeded() {
        let schema = r#"
        query {
          Q1 {
            Q2 {
              Q3 {
                url
              }
            }
          }
        }
        "#;
        let mut compiler = ApolloCompiler::new().recursion_limit(7);
        let doc_id = compiler.add_document(schema, "schema.graphql");
        let ast = compiler.db.ast(doc_id);
        assert_eq!(ast.recursion_limit().high, 4);
        assert_eq!(ast.errors().len(), 0);
        assert_eq!(ast.document().definitions().count(), 1);
    }
    #[test]
    fn it_errors_when_selection_set_token_limit_is_exceeded() {
        let schema = r#"
        type Query {
          field(arg1: Int, arg2: Int, arg3: Int, arg4: Int, arg5: Int, arg6: Int): Int
        }
        "#;
        let mut compiler = ApolloCompiler::new().token_limit(18);
        let doc_id = compiler.add_document(schema, "schema.graphql");
        let ast = compiler.db.ast(doc_id);
        assert_eq!(ast.errors().len(), 1);
        assert_eq!(
            ast.errors().next(),
            Some(&apollo_parser::Error::limit(
                "token limit reached, aborting lexing",
                55
            ))
        );
        assert_eq!(ast.document().definitions().count(), 1);
    }
    #[test]
    fn it_errors_with_multiple_limits() {
        let schema = r#"
            query {
                a {
                    a {
                        a {
                            a
                        }
                    }
                }
            }
        "#;
        let mut compiler = ApolloCompiler::new().token_limit(22).recursion_limit(10);
        let doc_id = compiler.add_document(schema, "schema.graphql");
        let ast = compiler.db.ast(doc_id);
        assert_eq!(ast.errors().len(), 1);
        assert_eq!(
            ast.errors().next(),
            Some(&apollo_parser::Error::limit(
                "token limit reached, aborting lexing",
                170
            ))
        );
        let mut compiler = ApolloCompiler::new().recursion_limit(3).token_limit(200);
        let doc_id = compiler.add_document(schema, "schema.graphql");
        let ast = compiler.db.ast(doc_id);
        assert_eq!(ast.errors().len(), 1);
        assert_eq!(
            ast.errors().next(),
            Some(&apollo_parser::Error::limit("parser limit(3) reached", 121))
        );
    }
    #[test]
    fn token_limit_with_multiple_sources() {
        let schema = r#"
type Query {
    website: URL,
    amount: Int
}
scalar URL @specifiedBy(url: "a.com");
"#;
        let query = "{ website }";
        let mut compiler = ApolloCompiler::new();
        compiler.add_type_system(schema, "schema.graphql");
        let ts = compiler.db.type_system();
        let mut compiler2 = ApolloCompiler::new().token_limit(2);
        compiler2.set_type_system_hir(ts);
        compiler2.add_executable(query, "query.graphql");
        let parser_errors = compiler2.db.syntax_errors();
        assert_eq!(parser_errors.len(), 1);
    }
}