use syntax::ast::{Literal, Pattern, RestPattern, StructKind, TypedPattern};
use syntax::program::Definition;
use syntax::types::Type;

use crate::Emitter;
use crate::go::expressions::values::convert_unicode_escapes;
use crate::go::names::generics;
use crate::go::patterns::decision_tree::{collect_pattern_info, emit_tree_bindings};
use crate::go::write_line;

pub(crate) fn emit_pattern_literal(literal: &Literal) -> String {
    match literal {
        Literal::Integer { value, text } => {
            if let Some(original) = text {
                original.clone()
            } else {
                value.to_string()
            }
        }
        Literal::Float { value, text } => text.clone().unwrap_or_else(|| value.to_string()),
        Literal::Boolean(b) => b.to_string(),
        Literal::String(s) => {
            let s = s.replace("\\0", "\\x00");
            let s = convert_unicode_escapes(&s);
            format!("\"{}\"", s)
        }
        Literal::Char(c) => {
            let c = c.replace("\\0", "\\x00");
            let c = convert_unicode_escapes(&c);
            format!("'{}'", c)
        }
        Literal::Imaginary(_) | Literal::FormatString(_) | Literal::Slice(_) => {
            unreachable!("FormatString, Slice, and Imaginary are not valid pattern literals")
        }
    }
}

impl Emitter<'_> {
    pub(crate) fn emit_pattern_bindings(
        &mut self,
        output: &mut String,
        subject: &str,
        pattern: &Pattern,
        typed: Option<&TypedPattern>,
    ) {
        let (_, bindings) = collect_pattern_info(self, pattern, typed);
        emit_tree_bindings(self, output, &bindings, subject);
    }

    pub(crate) fn fresh_var(&mut self, hint: Option<&str>) -> String {
        loop {
            self.scope.next_var += 1;
            let name = match hint {
                Some(h) => format!("{}_{}", h, self.scope.next_var),
                None => format!("tmp_{}", self.scope.next_var),
            };
            if !self.scope.bindings.has_go_name(&name) && !self.is_declared(&name) {
                return name;
            }
        }
    }

    pub(crate) fn pattern_has_bindings(pattern: &Pattern) -> bool {
        match pattern {
            Pattern::Identifier { .. } => true,
            Pattern::Tuple { elements, .. } => elements.iter().any(Self::pattern_has_bindings),
            Pattern::EnumVariant { fields, .. } => fields.iter().any(Self::pattern_has_bindings),
            Pattern::Struct { fields, .. } => {
                fields.iter().any(|f| Self::pattern_has_bindings(&f.value))
            }
            Pattern::Slice { prefix, rest, .. } => {
                prefix.iter().any(Self::pattern_has_bindings)
                    || matches!(rest, RestPattern::Bind { .. })
            }
            Pattern::Or { patterns, .. } => patterns.iter().any(Self::pattern_has_bindings),
            Pattern::WildCard { .. } | Pattern::Literal { .. } | Pattern::Unit { .. } => false,
        }
    }

    pub(crate) fn pattern_binds_name(pattern: &Pattern, name: &str) -> bool {
        match pattern {
            Pattern::Identifier { identifier, .. } => identifier == name,
            Pattern::Tuple { elements, .. } => {
                elements.iter().any(|e| Self::pattern_binds_name(e, name))
            }
            Pattern::EnumVariant { fields, .. } => {
                fields.iter().any(|f| Self::pattern_binds_name(f, name))
            }
            Pattern::Struct { fields, .. } => fields
                .iter()
                .any(|f| Self::pattern_binds_name(&f.value, name)),
            Pattern::Slice { prefix, rest, .. } => {
                prefix.iter().any(|e| Self::pattern_binds_name(e, name))
                    || matches!(rest, RestPattern::Bind { name: n, .. } if n == name)
            }
            Pattern::Or { patterns, .. } => {
                patterns.iter().any(|p| Self::pattern_binds_name(p, name))
            }
            Pattern::WildCard { .. } | Pattern::Literal { .. } | Pattern::Unit { .. } => false,
        }
    }

    pub(crate) fn pattern_has_binding_collisions(&self, pattern: &Pattern) -> bool {
        match pattern {
            Pattern::Identifier { .. } => false,
            Pattern::Tuple { elements, .. } => elements
                .iter()
                .any(|e| self.pattern_has_binding_collisions(e)),
            Pattern::EnumVariant { fields, .. } => fields
                .iter()
                .any(|f| self.pattern_has_binding_collisions(f)),
            Pattern::Struct { fields, .. } => fields
                .iter()
                .any(|f| self.pattern_has_binding_collisions(&f.value)),
            Pattern::Slice { prefix, rest, .. } => {
                prefix
                    .iter()
                    .any(|e| self.pattern_has_binding_collisions(e))
                    || if let RestPattern::Bind { name, .. } = rest {
                        !self.ctx.unused.is_unused_rest_binding(rest) && self.is_declared(name)
                    } else {
                        false
                    }
            }
            Pattern::Or { patterns, .. } => patterns
                .iter()
                .any(|p| self.pattern_has_binding_collisions(p)),
            Pattern::WildCard { .. } | Pattern::Literal { .. } | Pattern::Unit { .. } => false,
        }
    }

    pub(crate) fn is_catchall_pattern(pattern: &Pattern) -> bool {
        match pattern {
            Pattern::WildCard { .. } | Pattern::Identifier { .. } | Pattern::Unit { .. } => true,
            Pattern::Literal { .. } | Pattern::EnumVariant { .. } => false,
            Pattern::Struct { fields, rest, .. } => {
                *rest && fields.iter().all(|f| Self::is_catchall_pattern(&f.value))
            }
            Pattern::Tuple { elements, .. } => elements.iter().all(Self::is_catchall_pattern),
            Pattern::Slice { prefix, rest, .. } => prefix.is_empty() && rest.is_present(),
            Pattern::Or { patterns, .. } => patterns.iter().any(Self::is_catchall_pattern),
        }
    }

    pub(crate) fn emit_binding_declarations_with_type(
        &mut self,
        output: &mut String,
        pattern: &Pattern,
        ty: &Type,
        typed: Option<&TypedPattern>,
    ) {
        let resolved = ty.resolve();

        match (pattern, typed) {
            (Pattern::Identifier { identifier, .. }, _) => {
                if let Some(go_name) = self.go_name_for_binding(pattern) {
                    let go_name = if self.is_declared(&go_name) {
                        self.fresh_var(Some(identifier))
                    } else {
                        go_name
                    };
                    let go_name = self.scope.bindings.add(identifier, go_name);
                    self.declare(&go_name);
                    let go_ty = self.go_type_as_string(&resolved);
                    write_line!(output, "var {} {}", go_name, go_ty);
                } else {
                    self.scope.bindings.add(identifier, "_");
                }
            }

            (Pattern::Tuple { elements, .. }, _) => {
                let typed_elems = match typed {
                    Some(TypedPattern::Tuple { elements: te, .. }) => te.as_slice(),
                    _ => &[],
                };
                let elem_types: Option<&[Type]> = match &resolved {
                    Type::Constructor { params, .. } => Some(params),
                    Type::Tuple(elems) => Some(elems),
                    _ => None,
                };
                if let Some(types) = elem_types {
                    for (i, (elem, elem_ty)) in elements.iter().zip(types.iter()).enumerate() {
                        self.emit_binding_declarations_with_type(
                            output,
                            elem,
                            elem_ty,
                            typed_elems.get(i),
                        );
                    }
                }
            }

            (
                Pattern::Struct { fields, .. },
                Some(TypedPattern::Struct {
                    struct_name,
                    struct_fields,
                    pattern_fields: typed_pf,
                    ..
                }),
            ) => {
                if let Type::Constructor { params, .. } = &resolved
                    && let Some(Definition::Struct { generics, .. }) =
                        self.ctx.definitions.get(struct_name.as_str())
                {
                    for field_pattern in fields {
                        if let Some(field_definition) =
                            struct_fields.iter().find(|f| f.name == field_pattern.name)
                        {
                            let field_ty = generics::resolve_field_type(
                                generics,
                                params,
                                &field_definition.ty,
                            );
                            let typed_child = typed_pf
                                .iter()
                                .find(|(n, _)| n == &field_pattern.name)
                                .map(|(_, tp)| tp);
                            self.emit_binding_declarations_with_type(
                                output,
                                &field_pattern.value,
                                &field_ty,
                                typed_child,
                            );
                        }
                    }
                }
            }

            (
                Pattern::Struct { fields, .. },
                Some(TypedPattern::EnumStructVariant {
                    enum_name,
                    variant_fields,
                    pattern_fields: typed_pf,
                    ..
                }),
            ) => {
                if let Type::Constructor { params, .. } = &resolved
                    && let Some(Definition::Enum { generics, .. }) =
                        self.ctx.definitions.get(enum_name.as_str())
                {
                    for field_pattern in fields {
                        if let Some(field_definition) =
                            variant_fields.iter().find(|f| f.name == field_pattern.name)
                        {
                            let field_ty = generics::resolve_field_type(
                                generics,
                                params,
                                &field_definition.ty,
                            );
                            let typed_child = typed_pf
                                .iter()
                                .find(|(n, _)| n == &field_pattern.name)
                                .map(|(_, tp)| tp);
                            self.emit_binding_declarations_with_type(
                                output,
                                &field_pattern.value,
                                &field_ty,
                                typed_child,
                            );
                        }
                    }
                }
            }

            (
                Pattern::Struct {
                    fields, identifier, ..
                },
                _,
            ) => {
                if let Type::Constructor { id, params, .. } = &resolved {
                    if let Some(Definition::Struct {
                        fields: field_defs,
                        generics,
                        ..
                    }) = self.ctx.definitions.get(id.as_str())
                    {
                        for field_pattern in fields {
                            if let Some(field_definition) =
                                field_defs.iter().find(|f| f.name == field_pattern.name)
                            {
                                let field_ty = generics::resolve_field_type(
                                    generics,
                                    params,
                                    &field_definition.ty,
                                );
                                self.emit_binding_declarations_with_type(
                                    output,
                                    &field_pattern.value,
                                    &field_ty,
                                    None,
                                );
                            }
                        }
                    } else if let Some(Definition::Enum {
                        variants, generics, ..
                    }) = self.ctx.definitions.get(id.as_str())
                    {
                        let variant_name = identifier.split('.').next_back().unwrap_or(identifier);
                        if let Some(variant_definition) =
                            variants.iter().find(|v| v.name == variant_name)
                        {
                            for field_pattern in fields {
                                if let Some(field_definition) = variant_definition
                                    .fields
                                    .iter()
                                    .find(|f| f.name == field_pattern.name)
                                {
                                    let field_ty = generics::resolve_field_type(
                                        generics,
                                        params,
                                        &field_definition.ty,
                                    );
                                    self.emit_binding_declarations_with_type(
                                        output,
                                        &field_pattern.value,
                                        &field_ty,
                                        None,
                                    );
                                }
                            }
                        }
                    }
                }
            }

            (
                Pattern::EnumVariant {
                    fields,
                    ty: pattern_ty,
                    ..
                },
                Some(TypedPattern::EnumVariant {
                    enum_name,
                    variant_fields,
                    fields: typed_fields,
                    ..
                }),
            ) => {
                if self.is_tuple_struct_type(pattern_ty) {
                    if let Type::Constructor { id, params, .. } = &resolved
                        && let Some(Definition::Struct {
                            fields: field_defs,
                            generics,
                            kind: StructKind::Tuple,
                            ..
                        }) = self.ctx.definitions.get(id.as_str())
                    {
                        for (i, (field_pattern, field_definition)) in
                            fields.iter().zip(field_defs.iter()).enumerate()
                        {
                            let field_ty = generics::resolve_field_type(
                                generics,
                                params,
                                &field_definition.ty,
                            );
                            self.emit_binding_declarations_with_type(
                                output,
                                field_pattern,
                                &field_ty,
                                typed_fields.get(i),
                            );
                        }
                    }
                    return;
                }

                if let Type::Constructor { params, .. } = &resolved
                    && let Some(Definition::Enum { generics, .. }) =
                        self.ctx.definitions.get(enum_name.as_str())
                {
                    for (i, (field_pattern, field_definition)) in
                        fields.iter().zip(variant_fields.iter()).enumerate()
                    {
                        let field_ty =
                            generics::resolve_field_type(generics, params, &field_definition.ty);
                        self.emit_binding_declarations_with_type(
                            output,
                            field_pattern,
                            &field_ty,
                            typed_fields.get(i),
                        );
                    }
                }
            }

            (
                Pattern::EnumVariant {
                    identifier,
                    fields,
                    ty: pattern_ty,
                    ..
                },
                _,
            ) => {
                if self.is_tuple_struct_type(pattern_ty) {
                    if let Type::Constructor { id, params, .. } = &resolved
                        && let Some(Definition::Struct {
                            fields: field_defs,
                            generics,
                            kind: StructKind::Tuple,
                            ..
                        }) = self.ctx.definitions.get(id.as_str())
                    {
                        for (field_pattern, field_definition) in
                            fields.iter().zip(field_defs.iter())
                        {
                            let field_ty = generics::resolve_field_type(
                                generics,
                                params,
                                &field_definition.ty,
                            );
                            self.emit_binding_declarations_with_type(
                                output,
                                field_pattern,
                                &field_ty,
                                None,
                            );
                        }
                    }
                    return;
                }

                if let Type::Constructor { id, params, .. } = &resolved
                    && let Some(Definition::Enum {
                        variants, generics, ..
                    }) = self.ctx.definitions.get(id.as_str())
                {
                    let variant_name = identifier.split('.').next_back().unwrap_or(identifier);
                    if let Some(variant_definition) =
                        variants.iter().find(|v| v.name == variant_name)
                    {
                        for (field_pattern, field_definition) in
                            fields.iter().zip(variant_definition.fields.iter())
                        {
                            let field_ty = generics::resolve_field_type(
                                generics,
                                params,
                                &field_definition.ty,
                            );
                            self.emit_binding_declarations_with_type(
                                output,
                                field_pattern,
                                &field_ty,
                                None,
                            );
                        }
                    }
                }
            }

            (
                Pattern::Slice { prefix, rest, .. },
                Some(TypedPattern::Slice {
                    prefix: typed_prefix,
                    element_type,
                    ..
                }),
            ) => {
                for (i, elem) in prefix.iter().enumerate() {
                    self.emit_binding_declarations_with_type(
                        output,
                        elem,
                        element_type,
                        typed_prefix.get(i),
                    );
                }
                if let RestPattern::Bind { name, .. } = rest
                    && let Some(go_name) = self.go_name_for_rest_binding(rest)
                {
                    let go_name = self.scope.bindings.add(name, go_name);
                    let go_ty = self.go_type_as_string(&resolved);
                    write_line!(output, "var {} {}", go_name, go_ty);
                }
            }

            (Pattern::Slice { prefix, rest, .. }, _) => {
                if let Type::Constructor { params, .. } = &resolved
                    && let Some(elem_ty) = params.first()
                {
                    for elem in prefix {
                        self.emit_binding_declarations_with_type(output, elem, elem_ty, None);
                    }
                    if let RestPattern::Bind { name, .. } = rest
                        && let Some(go_name) = self.go_name_for_rest_binding(rest)
                    {
                        let go_name = self.scope.bindings.add(name, go_name);
                        let go_ty = self.go_type_as_string(&resolved);
                        write_line!(output, "var {} {}", go_name, go_ty);
                    }
                }
            }

            (Pattern::Or { patterns, .. }, Some(TypedPattern::Or { alternatives })) => {
                if let Some(first) = patterns.first() {
                    self.emit_binding_declarations_with_type(
                        output,
                        first,
                        ty,
                        alternatives.first(),
                    );
                }
            }

            (Pattern::Or { patterns, .. }, _) => {
                if let Some(first) = patterns.first() {
                    self.emit_binding_declarations_with_type(output, first, ty, None);
                }
            }

            (Pattern::WildCard { .. } | Pattern::Literal { .. } | Pattern::Unit { .. }, _) => {}
        }
    }
}
