use crate::checker::EnvResolve;
use crate::store::Store;
use syntax::ast::{Expression, FormatStringPart, Literal, Span};
use syntax::types::{SimpleKind, Type};

use crate::checker::infer::InferCtx;

impl InferCtx<'_, '_> {
    pub(super) fn infer_literal(
        &mut self,
        literal: Literal,
        expected_ty: &Type,
        span: Span,
    ) -> Expression {
        let store = self.store;
        match literal {
            Literal::Boolean(boolean) => {
                let resolved = expected_ty.resolve_in(&self.env);
                let ty = if adapts_to_named_type(&resolved, store, SimpleKind::Bool) {
                    resolved.clone()
                } else {
                    let bool_ty = self.type_bool();
                    self.unify(expected_ty, &bool_ty, &span);
                    bool_ty
                };

                Expression::Literal {
                    literal: Literal::Boolean(boolean),
                    ty,
                    span,
                }
            }

            Literal::Integer { value, text } => {
                let resolved = expected_ty.resolve_in(&self.env);
                let ty = if let Some(numeric) = numeric_adapt_target(&resolved, store) {
                    let is_pre_negated = text.as_deref().is_some_and(|t| t.starts_with('-'));
                    if is_pre_negated {
                        self.check_negative_magnitude_overflow(
                            value.wrapping_neg(),
                            &numeric,
                            span,
                        );
                    } else if !self.scopes.is_inside_negation() {
                        self.check_integer_literal_overflow(value, &numeric, span);
                    }
                    resolved.clone()
                } else {
                    let int_ty = self.type_int();
                    self.unify(expected_ty, &int_ty, &span);
                    int_ty
                };

                Expression::Literal {
                    literal: Literal::Integer { value, text },
                    ty,
                    span,
                }
            }

            Literal::Float { value, text } => {
                let resolved = expected_ty.resolve_in(&self.env);
                let ty = if numeric_adapt_target(&resolved, store).is_some_and(|n| n.is_float()) {
                    self.check_float_literal_overflow(value, &resolved, span);
                    resolved.clone()
                } else {
                    let float_ty = self.type_float();
                    self.unify(expected_ty, &float_ty, &span);
                    float_ty
                };

                Expression::Literal {
                    literal: Literal::Float { value, text },
                    ty,
                    span,
                }
            }

            Literal::Imaginary(coef) => {
                let complex_ty = self.type_complex128();
                self.unify(expected_ty, &complex_ty, &span);

                Expression::Literal {
                    literal: Literal::Imaginary(coef),
                    ty: complex_ty,
                    span,
                }
            }

            Literal::String { value, raw } => {
                let resolved = expected_ty.resolve_in(&self.env);
                let ty = if adapts_to_named_type(&resolved, store, SimpleKind::String) {
                    resolved.clone()
                } else {
                    let string_ty = self.type_string();
                    self.unify(expected_ty, &string_ty, &span);
                    string_ty
                };

                Expression::Literal {
                    literal: Literal::String { value, raw },
                    ty,
                    span,
                }
            }

            Literal::Char(char) => {
                let resolved = expected_ty.resolve_in(&self.env);
                let ty = if let Some(numeric) = numeric_adapt_target(&resolved, store) {
                    if let Some(codepoint) = char_literal_codepoint(&char) {
                        self.check_integer_literal_overflow(codepoint, &numeric, span);
                    }
                    resolved.clone()
                } else {
                    let char_ty = self.type_char();
                    self.unify(expected_ty, &char_ty, &span);
                    char_ty
                };

                Expression::Literal {
                    literal: Literal::Char(char),
                    ty,
                    span,
                }
            }

            Literal::Slice(elements) => {
                // If expected type is Slice<T>, propagate T to element inference
                // so literals can adapt (e.g., `let x: Slice<int8> = [1, 2, 3]` works)
                let resolved = expected_ty.resolve_in(&self.env);
                let element_expected_ty = if resolved.get_name() == Some("Slice") {
                    resolved
                        .inner()
                        .unwrap_or_else(|| self.new_type_var_with_hint("T"))
                } else {
                    self.new_type_var_with_hint("T")
                };

                let new_elements: Vec<Expression> = elements
                    .into_iter()
                    .map(|e| {
                        self.with_value_context(|s| s.infer_expression(e, &element_expected_ty))
                    })
                    .collect();

                let slice_ty = self.type_slice(element_expected_ty);
                self.unify(expected_ty, &slice_ty, &span);

                Expression::Literal {
                    literal: Literal::Slice(new_elements),
                    ty: slice_ty,
                    span,
                }
            }

            Literal::FormatString(parts) => {
                let is_single_expression = parts.len() == 1
                    && matches!(parts.first(), Some(FormatStringPart::Expression(_)));

                let new_parts: Vec<_> = parts
                    .into_iter()
                    .map(|part| match part {
                        FormatStringPart::Text(text) => FormatStringPart::Text(text),
                        FormatStringPart::Expression(expression) => {
                            let type_var = self.new_type_var();
                            let inferred_expression = self.infer_expression(*expression, &type_var);
                            FormatStringPart::Expression(Box::new(inferred_expression))
                        }
                    })
                    .collect();

                if is_single_expression
                    && let Some(FormatStringPart::Expression(expression)) = new_parts.first()
                    && expression.get_type().resolve_in(&self.env).is_string()
                {
                    self.facts.add_expression_only_fstring(span);
                }

                let string_ty = self.type_string();
                self.unify(expected_ty, &string_ty, &span);

                Expression::Literal {
                    literal: Literal::FormatString(new_parts),
                    ty: string_ty,
                    span,
                }
            }
        }
    }

    pub(super) fn infer_unit(&mut self, span: Span, expected_ty: &Type) -> Expression {
        let new_ty = self.new_type_var();
        let unit_ty = self.type_unit();
        self.unify(&new_ty, &unit_ty, &span);
        self.unify(expected_ty, &new_ty, &span);
        Expression::Unit { ty: new_ty, span }
    }
}

fn numeric_adapt_target(ty: &Type, store: &Store) -> Option<Type> {
    store.deep_resolve_alias(ty).literal_adaptation_target()
}

fn adapts_to_named_type(ty: &Type, store: &Store, kind: SimpleKind) -> bool {
    let peeled = store.deep_resolve_alias(ty);
    matches!(&peeled, Type::Nominal { id, .. }
        if store.is_nominal_defined_type(id.as_str())
            && peeled.underlying_simple_kind() == Some(kind))
}

fn char_literal_codepoint(s: &str) -> Option<u64> {
    if let Some(rest) = s.strip_prefix('\\') {
        match rest.as_bytes().first()? {
            b'n' => Some(10),
            b't' => Some(9),
            b'r' => Some(13),
            b'0' => Some(0),
            b'\\' => Some(92),
            b'\'' => Some(39),
            b'x' => u64::from_str_radix(&rest[1..], 16).ok(),
            _ => None,
        }
    } else {
        s.chars().next().map(|c| c as u64)
    }
}
