use super::{full_path_prefix, BoundQuery, Query, QueryValidationError, Selection, SelectionId};
use crate::schema::TypeId;

pub(super) fn validate_typename_presence(
    query: &BoundQuery<'_>,
) -> Result<(), QueryValidationError> {
    for fragment in query.query.fragments.iter() {
        let type_id = match fragment.on {
            id @ TypeId::Interface(_) | id @ TypeId::Union(_) => id,
            _ => continue,
        };

        if !selection_set_contains_type_name(fragment.on, &fragment.selection_set, query.query) {
            return Err(QueryValidationError::new(format!(
                "The `{}` fragment uses `{}` but does not select `__typename` on it. graphql-client cannot generate code for it. Please add `__typename` to the selection.",
                &fragment.name,
                type_id.name(query.schema),
            )));
        }
    }

    let union_and_interface_field_selections =
        query
            .query
            .selections()
            .filter_map(|(selection_id, selection)| match selection {
                Selection::Field(field) => match query.schema.get_field(field.field_id).r#type.id {
                    id @ TypeId::Interface(_) | id @ TypeId::Union(_) => {
                        Some((selection_id, id, &field.selection_set))
                    }
                    _ => None,
                },
                _ => None,
            });

    for selection in union_and_interface_field_selections {
        if !selection_set_contains_type_name(selection.1, selection.2, query.query) {
            return Err(QueryValidationError::new(format!(
                "The query uses `{path}` at `{selected_type}` but does not select `__typename` on it. graphql-client cannot generate code for it. Please add `__typename` to the selection.",
                path = full_path_prefix(selection.0, query),
                selected_type = selection.1.name(query.schema)
            )));
        }
    }

    Ok(())
}

fn selection_set_contains_type_name(
    parent_type_id: TypeId,
    selection_set: &[SelectionId],
    query: &Query,
) -> bool {
    for id in selection_set {
        let selection = query.get_selection(*id);

        match selection {
            Selection::Typename => return true,
            Selection::FragmentSpread(fragment_id) => {
                let fragment = query.get_fragment(*fragment_id);
                if fragment.on == parent_type_id
                    && selection_set_contains_type_name(fragment.on, &fragment.selection_set, query)
                {
                    return true;
                }
            }
            _ => (),
        }
    }

    false
}
