use crate::Emitter;
use crate::names::go_name;
use syntax::ast::{Expression, Generic, Literal, UnaryOperator};
use syntax::types::Type;

impl Emitter<'_> {
    pub(crate) fn emit_type_alias(
        &mut self,
        name: &str,
        generics: &[Generic],
        ty: &Type,
    ) -> String {
        let is_fn_alias;
        let underlying = match ty {
            Type::Forall { body, .. } => match body.as_ref() {
                Type::Nominal {
                    underlying_ty: Some(inner),
                    ..
                } if matches!(inner.as_ref(), Type::Function { .. }) => {
                    is_fn_alias = true;
                    inner.as_ref()
                }
                other => {
                    is_fn_alias = false;
                    other
                }
            },
            Type::Nominal {
                underlying_ty: Some(inner),
                ..
            } if matches!(inner.as_ref(), Type::Function { .. }) => {
                is_fn_alias = true;
                inner.as_ref()
            }
            _ => {
                is_fn_alias = false;
                ty
            }
        };
        let ty_string = self.go_type_as_string(underlying);

        if let Type::Nominal { id, .. } = underlying
            && let Some((module, _)) = id.split_once('.')
            && module != self.current_module
            && module != go_name::PRELUDE_MODULE
            && !go_name::is_go_import(module)
        {
            self.require_module_import(module);
        }

        let generic_names: Vec<&str> = generics.iter().map(|g| g.name.as_ref()).collect();
        let map_key_generics =
            Self::collect_map_key_generics(std::iter::once(underlying), &generic_names);
        let generics_string = self.generics_to_string_with_map_keys(generics, &map_key_generics);

        let separator = if is_fn_alias { " " } else { " = " };
        format!(
            "type {}{}{}{}",
            go_name::escape_keyword(name),
            generics_string,
            separator,
            ty_string
        )
    }

    pub(crate) fn emit_const(
        &mut self,
        identifier: &str,
        expression: &Expression,
        ty: &Type,
    ) -> String {
        let go_identifier = self.scope.bindings.add(identifier, identifier);
        let ty_str = self.go_type_as_string(ty);

        let mut output = String::new();
        let expression_string = self.emit_operand(&mut output, expression);
        let value = if expression_string.is_empty() {
            "struct{}{}"
        } else {
            &expression_string
        };
        let keyword = if self.is_go_const_eligible(expression) {
            self.scope.go_const_bindings.insert(go_identifier.clone());
            "const"
        } else {
            "var"
        };
        format!("{} {} {} = {}", keyword, go_identifier, ty_str, value)
    }

    fn is_go_const_eligible(&self, expression: &Expression) -> bool {
        match expression.unwrap_parens() {
            Expression::Literal { literal, .. } => matches!(
                literal,
                Literal::Integer { .. }
                    | Literal::Float { .. }
                    | Literal::Imaginary(_)
                    | Literal::Boolean(_)
                    | Literal::String { .. }
                    | Literal::Char(_)
            ),
            Expression::Identifier { value, .. } => {
                self.scope.go_const_bindings.contains(value.as_str())
            }
            Expression::Binary { left, right, .. } => {
                self.is_go_const_eligible(left) && self.is_go_const_eligible(right)
            }
            Expression::Unary {
                operator: UnaryOperator::Negative | UnaryOperator::Not,
                expression,
                ..
            } => self.is_go_const_eligible(expression),
            _ => false,
        }
    }
}
