use log::debug;
use rue_ast::AstListExpr;
use rue_diagnostic::DiagnosticKind;
use rue_hir::{Hir, Value};
use rue_types::{Pair, Type, TypeId, Union};

use crate::{Compiler, compile_expr};

pub fn compile_list_expr(
    ctx: &mut Compiler,
    list: &AstListExpr,
    mut expected_type: Option<TypeId>,
) -> Value {
    let mut values = Vec::new();
    let mut nil_terminated = true;

    let len = list.items().count();

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

        let item_type = if is_spread {
            nil_terminated = false;
            expected_type
        } else if let Some(ty) = expected_type
            && let pairs = rue_types::extract_pairs(ctx.types_mut(), ty, false)
            && !pairs.is_empty()
        {
            let first = if pairs.len() == 1 {
                pairs[0].first
            } else {
                ctx.alloc_type(Type::Union(Union::new(
                    pairs.iter().map(|pair| pair.first).collect(),
                )))
            };

            let rest = if pairs.len() == 1 {
                pairs[0].rest
            } else {
                ctx.alloc_type(Type::Union(Union::new(
                    pairs.iter().map(|pair| pair.rest).collect(),
                )))
            };

            expected_type = Some(rest);

            Some(first)
        } else {
            None
        };

        let value = if let Some(expr) = item.expr() {
            compile_expr(ctx, &expr, item_type)
        } else {
            debug!("Unresolved list item expr");
            ctx.builtins().unresolved.clone()
        };

        values.push(value);
    }

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

    for (i, value) in values.into_iter().rev().enumerate() {
        if !nil_terminated && i == 0 {
            hir = value.hir;
            ty = value.ty;
            continue;
        }
        hir = ctx.alloc_hir(Hir::Pair(value.hir, hir));
        ty = ctx.alloc_type(Type::Pair(Pair::new(value.ty, ty)));
    }

    Value::new(hir, ty)
}
