use crate::*;
use apollo_compiler::hir;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum FromHirError {
    #[error("float conversion error")]
    FloatCoercionError,
}
impl TryFrom<&hir::ObjectTypeDefinition> for ObjectDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::ObjectTypeDefinition) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let mut def = ObjectDefinition::new(name);
        if let Some(description) = value.description().map(str::to_string) {
            def.description(description);
        }
        for interface in value.implements_interfaces() {
            def.interface(interface.interface().to_owned());
        }
        for field in value.self_fields() {
            def.field(field.try_into()?);
        }
        for directive in value.self_directives() {
            def.directive(directive.try_into()?);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::ObjectTypeExtension> for ObjectDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::ObjectTypeExtension) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let mut def = ObjectDefinition::new(name);
        def.extend();
        for interface in value.implements_interfaces() {
            def.interface(interface.interface().to_owned());
        }
        for field in value.fields() {
            def.field(field.try_into()?);
        }
        for directive in value.directives() {
            def.directive(directive.try_into()?);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::InterfaceTypeDefinition> for InterfaceDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::InterfaceTypeDefinition) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let mut def = InterfaceDefinition::new(name);
        if let Some(description) = value.description().map(str::to_string) {
            def.description(description);
        }
        for interface in value.implements_interfaces() {
            def.interface(interface.interface().to_owned());
        }
        for field in value.self_fields() {
            def.field(field.try_into()?);
        }
        for directive in value.self_directives() {
            def.directive(directive.try_into()?);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::InterfaceTypeExtension> for InterfaceDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::InterfaceTypeExtension) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let mut def = InterfaceDefinition::new(name);
        def.extend();
        for interface in value.implements_interfaces() {
            def.interface(interface.interface().to_owned());
        }
        for field in value.fields() {
            def.field(field.try_into()?);
        }
        for directive in value.directives() {
            def.directive(directive.try_into()?);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::ScalarTypeDefinition> for ScalarDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::ScalarTypeDefinition) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let mut def = ScalarDefinition::new(name);
        if let Some(description) = value.description().map(str::to_string) {
            def.description(description);
        }
        for directive in value.self_directives() {
            def.directive(directive.try_into()?);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::ScalarTypeExtension> for ScalarDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::ScalarTypeExtension) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let mut def = ScalarDefinition::new(name);
        def.extend();
        for directive in value.directives() {
            def.directive(directive.try_into()?);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::UnionTypeDefinition> for UnionDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::UnionTypeDefinition) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let mut def = UnionDefinition::new(name);
        if let Some(description) = value.description().map(str::to_string) {
            def.description(description);
        }
        for member in value.members() {
            def.member(member.name().to_owned());
        }
        for directive in value.self_directives() {
            def.directive(directive.try_into()?);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::UnionTypeExtension> for UnionDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::UnionTypeExtension) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let mut def = UnionDefinition::new(name);
        def.extend();
        for member in value.members() {
            def.member(member.name().to_owned());
        }
        for directive in value.directives() {
            def.directive(directive.try_into()?);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::EnumTypeDefinition> for EnumDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::EnumTypeDefinition) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let mut def = EnumDefinition::new(name);
        for value in value.self_values() {
            def.value(value.try_into()?);
        }
        for directive in value.self_directives() {
            def.directive(directive.try_into()?);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::EnumTypeExtension> for EnumDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::EnumTypeExtension) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let mut def = EnumDefinition::new(name);
        def.extend();
        for value in value.values() {
            def.value(value.try_into()?);
        }
        for directive in value.directives() {
            def.directive(directive.try_into()?);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::EnumValueDefinition> for EnumValue {
    type Error = FromHirError;
    fn try_from(value: &hir::EnumValueDefinition) -> Result<Self, Self::Error> {
        let enum_value = value.enum_value().to_owned();
        let mut def = EnumValue::new(enum_value);
        if let Some(description) = value.description().map(str::to_string) {
            def.description(description);
        }
        for directive in value.directives() {
            def.directive(directive.try_into()?);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::InputObjectTypeDefinition> for InputObjectDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::InputObjectTypeDefinition) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let mut def = InputObjectDefinition::new(name);
        if let Some(description) = value.description().map(str::to_string) {
            def.description(description);
        }
        for directive in value.self_directives() {
            def.directive(directive.try_into()?);
        }
        for input_field in value.self_fields() {
            def.field(input_field.try_into()?);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::InputObjectTypeExtension> for InputObjectDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::InputObjectTypeExtension) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let mut def = InputObjectDefinition::new(name);
        def.extend();
        for directive in value.directives() {
            def.directive(directive.try_into()?);
        }
        for input_field in value.fields() {
            def.field(input_field.try_into()?);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::InputValueDefinition> for InputField {
    type Error = FromHirError;
    fn try_from(value: &hir::InputValueDefinition) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let typ = value.ty().try_into()?;
        let mut def = InputField::new(name, typ);
        if let Some(description) = value.description().map(str::to_string) {
            def.description(description);
        }
        for directive in value.directives() {
            def.directive(directive.try_into()?);
        }
        if let Some(default_value) = value.default_value() {
            let encoder_value: Value = default_value.try_into()?;
            let value_str = format!("{}", encoder_value); def.default_value(value_str);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::FieldDefinition> for FieldDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::FieldDefinition) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let field_type = value.ty().try_into()?;
        let mut def = FieldDefinition::new(name, field_type);
        if let Some(description) = value.description().map(str::to_string) {
            def.description(description);
        }
        for iv_def in value.arguments().input_values() {
            def.arg(iv_def.try_into()?);
        }
        for directive in value.directives() {
            def.directive(directive.try_into()?);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::Type> for Type_ {
    type Error = FromHirError;
    fn try_from(value: &hir::Type) -> Result<Self, Self::Error> {
        let ty = match value {
            hir::Type::NonNull { ty: hir_ty, .. } => Type_::NonNull {
                ty: Box::new(hir_ty.as_ref().try_into()?),
            },
            hir::Type::List { ty: hir_ty, .. } => Type_::List {
                ty: Box::new(hir_ty.as_ref().try_into()?),
            },
            hir::Type::Named { name, .. } => Type_::NamedType {
                name: name.to_owned(),
            },
        };
        Ok(ty)
    }
}
impl TryFrom<&hir::Directive> for Directive {
    type Error = FromHirError;
    fn try_from(value: &hir::Directive) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let mut directive = Directive::new(name);
        for arg in value.arguments() {
            directive.arg(arg.try_into()?);
        }
        Ok(directive)
    }
}
impl TryFrom<&hir::Argument> for Argument {
    type Error = FromHirError;
    fn try_from(value: &hir::Argument) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let value = value.value().try_into()?;
        let arg = Argument::new(name, value);
        Ok(arg)
    }
}
impl TryFrom<&hir::Value> for Value {
    type Error = FromHirError;
    fn try_from(value: &hir::Value) -> Result<Self, Self::Error> {
        let value = match value {
            hir::Value::Variable(v) => Value::Variable(v.name().to_owned()),
            hir::Value::Int { value: i, .. } => {
                Value::Int(i.to_i32_checked().ok_or(FromHirError::FloatCoercionError)?)
            }
            hir::Value::Float { value: f, .. } => Value::Float(f.get()),
            hir::Value::String { value: s, .. } => Value::String(s.clone()),
            hir::Value::Boolean { value: b, .. } => Value::Boolean(*b),
            hir::Value::Null { .. } => Value::Null,
            hir::Value::Enum { value: e, .. } => Value::Enum(e.src().to_owned()),
            hir::Value::List { value: l, .. } => Value::List(
                l.iter()
                    .map(TryInto::<Value>::try_into)
                    .collect::<Result<Vec<_>, FromHirError>>()?,
            ),
            hir::Value::Object { value: fields, .. } => Value::Object(
                fields
                    .iter()
                    .map(|(n, v)| v.try_into().map(|v: Value| (n.src().to_owned(), v)))
                    .collect::<Result<Vec<_>, FromHirError>>()?,
            ),
        };
        Ok(value)
    }
}
impl TryFrom<&hir::InputValueDefinition> for InputValueDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::InputValueDefinition) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let iv_type = value.ty().try_into()?;
        let mut def = InputValueDefinition::new(name, iv_type);
        if let Some(description) = value.description().map(str::to_string) {
            def.description(description);
        }
        for directive in value.directives() {
            def.directive(directive.try_into()?);
        }
        if let Some(default_value) = value.default_value() {
            let encoder_value: Value = default_value.try_into()?;
            let value_str = format!("{}", encoder_value); def.default_value(value_str);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::DirectiveDefinition> for DirectiveDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::DirectiveDefinition) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let mut def = DirectiveDefinition::new(name);
        if let Some(description) = value.description().map(str::to_string) {
            def.description(description);
        }
        if value.repeatable() {
            def.repeatable();
        }
        for arg in value.arguments().input_values() {
            def.arg(arg.try_into()?);
        }
        for directive_loc in value.directive_locations() {
            def.location(directive_loc.name().to_owned());
        }
        Ok(def)
    }
}
impl TryFrom<&hir::FragmentDefinition> for FragmentDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::FragmentDefinition) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let type_cond = value.type_condition().to_owned();
        let selection_set = value.selection_set().try_into()?;
        let def = FragmentDefinition::new(name, TypeCondition::new(type_cond), selection_set);
        Ok(def)
    }
}
impl TryFrom<&hir::SelectionSet> for SelectionSet {
    type Error = FromHirError;
    fn try_from(value: &hir::SelectionSet) -> Result<Self, Self::Error> {
        let mut selection_set = SelectionSet::new();
        for selection in value.selection() {
            selection_set.selection(selection.try_into()?)
        }
        Ok(selection_set)
    }
}
impl TryFrom<&hir::Selection> for Selection {
    type Error = FromHirError;
    fn try_from(value: &hir::Selection) -> Result<Self, Self::Error> {
        let selection = match value {
            hir::Selection::Field(field) => Selection::Field(field.as_ref().try_into()?),
            hir::Selection::FragmentSpread(fragment) => {
                Selection::FragmentSpread(fragment.as_ref().try_into()?)
            }
            hir::Selection::InlineFragment(fragment) => {
                Selection::InlineFragment(fragment.as_ref().try_into()?)
            }
        };
        Ok(selection)
    }
}
impl TryFrom<&hir::Field> for Field {
    type Error = FromHirError;
    fn try_from(value: &hir::Field) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let mut field = Field::new(name);
        field.alias(value.alias().map(|a| a.0.clone()));
        for arg in value.arguments() {
            field.argument(arg.try_into()?);
        }
        for directive in value.directives() {
            field.directive(directive.try_into()?);
        }
        if !value.selection_set().selection().is_empty() {
            field.selection_set(Some(value.selection_set().try_into()?));
        }
        Ok(field)
    }
}
impl TryFrom<&hir::FragmentSpread> for FragmentSpread {
    type Error = FromHirError;
    fn try_from(value: &hir::FragmentSpread) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let mut fragment = FragmentSpread::new(name);
        for directive in value.directives() {
            fragment.directive(directive.try_into()?);
        }
        Ok(fragment)
    }
}
impl TryFrom<&hir::InlineFragment> for InlineFragment {
    type Error = FromHirError;
    fn try_from(value: &hir::InlineFragment) -> Result<Self, Self::Error> {
        let selection_set = value.selection_set().try_into()?;
        let mut fragment = InlineFragment::new(selection_set);
        fragment.type_condition(
            value
                .type_condition()
                .map(|tc| TypeCondition::new(tc.to_owned())),
        );
        for directive in value.directives() {
            fragment.directive(directive.try_into()?);
        }
        Ok(fragment)
    }
}
impl TryFrom<&hir::VariableDefinition> for VariableDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::VariableDefinition) -> Result<Self, Self::Error> {
        let name = value.name().to_owned();
        let ty = value.ty().try_into()?;
        let mut def = VariableDefinition::new(name, ty);
        if let Some(default_value) = value.default_value() {
            def.default_value(default_value.try_into()?);
        }
        Ok(def)
    }
}
impl TryFrom<&hir::OperationDefinition> for OperationDefinition {
    type Error = FromHirError;
    fn try_from(value: &hir::OperationDefinition) -> Result<Self, Self::Error> {
        let operation_type = value.operation_ty().try_into()?;
        let selection_set = value.selection_set().try_into()?;
        let mut def = OperationDefinition::new(operation_type, selection_set);
        for directive in value.directives() {
            def.directive(directive.try_into()?);
        }
        for var in value.variables() {
            def.variable_definition(var.try_into()?);
        }
        Ok(def)
    }
}
impl TryInto<OperationType> for hir::OperationType {
    type Error = FromHirError;
    fn try_into(self) -> Result<OperationType, Self::Error> {
        Ok(match self {
            hir::OperationType::Query => OperationType::Query,
            hir::OperationType::Mutation => OperationType::Mutation,
            hir::OperationType::Subscription => OperationType::Subscription,
        })
    }
}