use graphgate_schema::TypeExt;
use parser::types::{FragmentDefinition, InlineFragment, TypeCondition, VariableDefinition};
use parser::{Pos, Positioned};
use value::Name;

use crate::{Visitor, VisitorContext};

#[derive(Default)]
pub struct KnownTypeNames;

impl<'a> Visitor<'a> for KnownTypeNames {
    fn enter_fragment_definition(
        &mut self,
        ctx: &mut VisitorContext<'a>,
        _name: &'a Name,
        fragment_definition: &'a Positioned<FragmentDefinition>,
    ) {
        let TypeCondition { on: name } = &fragment_definition.node.type_condition.node;
        validate_type(ctx, &name.node, fragment_definition.pos);
    }

    fn enter_variable_definition(
        &mut self,
        ctx: &mut VisitorContext<'a>,
        variable_definition: &'a Positioned<VariableDefinition>,
    ) {
        validate_type(
            ctx,
            variable_definition.node.var_type.node.concrete_typename(),
            variable_definition.pos,
        );
    }

    fn enter_inline_fragment(
        &mut self,
        ctx: &mut VisitorContext<'a>,
        inline_fragment: &'a Positioned<InlineFragment>,
    ) {
        if let Some(TypeCondition { on: name }) = inline_fragment
            .node
            .type_condition
            .as_ref()
            .map(|c| &c.node)
        {
            validate_type(ctx, &name.node, inline_fragment.pos);
        }
    }
}

fn validate_type(ctx: &mut VisitorContext<'_>, type_name: &str, pos: Pos) {
    if !ctx.schema.types.contains_key(type_name) {
        ctx.report_error(vec![pos], format!(r#"Unknown type "{}""#, type_name));
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    pub fn factory() -> KnownTypeNames {
        KnownTypeNames::default()
    }

    #[test]
    fn known_type_names_are_valid() {
        expect_passes_rule!(
            factory,
            r#"
          query Foo($var: String, $required: [String!]!) {
            user(id: 4) {
              pets { ... on Pet { name }, ...PetFields, ... { name } }
            }
          }
          fragment PetFields on Pet {
            name
          }
        "#,
        );
    }

    #[test]
    fn unknown_type_names_are_invalid() {
        expect_fails_rule!(
            factory,
            r#"
          query Foo($var: JumbledUpLetters) {
            user(id: 4) {
              name
              pets { ... on Badger { name }, ...PetFields }
            }
          }
          fragment PetFields on Peettt {
            name
          }
        "#,
        );
    }
}
