use indexmap::IndexSet;
use log::debug;
use rue_ast::{AstNode, AstStructItem};
use rue_diagnostic::DiagnosticKind;
use rue_hir::{Declaration, ScopeId};
use rue_types::{Pair, Struct, Type, TypeId};

use crate::{
    Compiler, CompletionContext, SyntaxField, SyntaxItemKind, compile_expr,
    compile_generic_parameters, compile_type,
};

pub fn declare_struct_item(ctx: &mut Compiler, struct_item: &AstStructItem) -> (TypeId, ScopeId) {
    ctx.add_syntax(
        SyntaxItemKind::CompletionContext(CompletionContext::Item),
        struct_item.syntax().text_range(),
    );

    let ty = ctx.alloc_type(Type::Unresolved);

    ctx.push_declaration(Declaration::Type(ty));

    let scope = ctx.alloc_child_scope();

    let generics = if let Some(generic_parameters) = struct_item.generic_parameters() {
        compile_generic_parameters(ctx, scope, &generic_parameters)
    } else {
        vec![]
    };

    let inner = ctx.alloc_type(Type::Unresolved);

    let mut fields = IndexSet::new();
    let mut nil_terminated = true;

    let len = struct_item.fields().count();

    for (i, field) in struct_item.fields().enumerate() {
        let is_spread = if let Some(spread) = field.spread() {
            if i == len - 1 {
                true
            } else {
                ctx.diagnostic(&spread, DiagnosticKind::NonFinalSpread);
                false
            }
        } else {
            false
        };

        if is_spread {
            nil_terminated = false;
        }

        if let Some(name) = field.name()
            && !fields.insert(name.text().to_string())
        {
            ctx.diagnostic(
                &name,
                DiagnosticKind::DuplicateField(name.text().to_string()),
            );
        }
    }

    let name = struct_item.name().map(|name| ctx.local_name(&name));

    *ctx.ty_mut(ty) = Type::Struct(Struct {
        name,
        semantic: ty,
        inner,
        generics,
        fields,
        nil_terminated,
    });

    if let Some(name) = struct_item.name() {
        if ctx.last_scope().ty(name.text()).is_some() {
            ctx.diagnostic(
                &name,
                DiagnosticKind::DuplicateType(name.text().to_string()),
            );
        }

        ctx.last_scope_mut().insert_type(
            name.text().to_string(),
            ty,
            struct_item.export().is_some(),
        );

        ctx.declaration_span(Declaration::Type(ty), name.text_range());
    }

    ctx.pop_declaration();

    (ty, scope)
}

pub fn compile_struct_item(
    ctx: &mut Compiler,
    struct_item: &AstStructItem,
    struct_type: TypeId,
    scope: ScopeId,
) {
    ctx.push_declaration(Declaration::Type(struct_type));

    let nil_terminated = if let Type::Struct(Struct { nil_terminated, .. }) = ctx.ty(struct_type) {
        *nil_terminated
    } else {
        unreachable!()
    };

    let range = struct_item.syntax().text_range();
    ctx.push_scope(scope, range.start());

    let mut types = Vec::new();
    let mut names = IndexSet::new();

    for field in struct_item.fields() {
        let expected_type = field.ty().map(|ty| compile_type(ctx, &ty));

        let field_type = if let Some(expr) = field.expr() {
            let value = compile_expr(ctx, &expr, expected_type);

            if let Some(name) = field.name() {
                ctx.insert_default_field(struct_type, name.text().to_string(), value.clone());
            }

            expected_type.unwrap_or(value.ty)
        } else if let Some(ty) = expected_type {
            ty
        } else {
            debug!("Unresolved struct item field expr");
            ctx.builtins().unresolved.ty
        };

        let Some(name) = field.name() else {
            continue;
        };

        if names.insert(name.text().to_string()) {
            types.push(field_type);
        }

        ctx.add_syntax(
            SyntaxItemKind::FieldDeclaration(SyntaxField {
                name: name.text().to_string(),
                container: struct_type,
                ty: field_type,
            }),
            name.text_range(),
        );
    }

    ctx.pop_scope(range.end());

    let mut resolved_inner = ctx.builtins().nil.ty;

    for (i, ty) in types.into_iter().rev().enumerate() {
        if !nil_terminated && i == 0 {
            resolved_inner = ty;
        } else {
            resolved_inner = ctx.alloc_type(Type::Pair(Pair::new(ty, resolved_inner)));
        }
    }

    let Type::Struct(Struct { inner, .. }) = ctx.ty(struct_type) else {
        unreachable!()
    };

    let inner = *inner;

    *ctx.ty_mut(inner) = Type::Ref(resolved_inner);

    ctx.pop_declaration();
}
